@cortexkit/opencode-magic-context 0.33.1 → 0.34.0
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/config/agent-disable.d.ts +22 -0
- package/dist/config/agent-disable.d.ts.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +7 -0
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +82 -2
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/config/transform-mode.d.ts +2 -0
- package/dist/config/transform-mode.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-n48c783v.js → conflict-warning-hook-fvpfvt0w.js} +1 -1
- package/dist/{derive-budgets-e9j09sjr.js → derive-budgets-9crp0cvd.js} +1 -1
- package/dist/features/builtin-commands/commands.d.ts +1 -1
- package/dist/features/builtin-commands/commands.d.ts.map +1 -1
- package/dist/features/magic-context/compaction-marker.d.ts +57 -0
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/fail-closed-block.d.ts +10 -0
- package/dist/features/magic-context/fail-closed-block.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts +7 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity.d.ts +2 -6
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
- package/dist/features/magic-context/mural/render-mural.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +8 -1
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/schema-fence-probe.d.ts +28 -0
- package/dist/features/magic-context/schema-fence-probe.d.ts.map +1 -0
- package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +35 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +38 -4
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
- package/dist/hooks/magic-context/boundary-execution.d.ts +1 -1
- package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +1 -0
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/child-session-spawn.d.ts +26 -0
- package/dist/hooks/magic-context/child-session-spawn.d.ts.map +1 -0
- package/dist/hooks/magic-context/command-handler.d.ts +2 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-off-transition.d.ts +96 -0
- package/dist/hooks/magic-context/compaction-off-transition.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-mapping.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +12 -5
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +47 -1
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +2 -2
- package/dist/hooks/magic-context/event-handler.d.ts +7 -0
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +13 -21
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts +1 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +11 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +9 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +3 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/send-session-notification.d.ts +20 -1
- package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -0
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +30 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +27 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-ey1bc14z.js → index-b563sga5.js} +7 -264
- package/dist/{index-y1c9tcm3.js → index-cn960ffd.js} +101 -20
- package/dist/{index-xyaekybw.js → index-dtas91s5.js} +6 -4
- package/dist/index-fcyvznch.js +279 -0
- package/dist/{index-63dq2g4d.js → index-ry8rh8cv.js} +138 -24
- package/dist/{index-c2gahrks.js → index-s0mdn690.js} +1599 -892
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1292 -623
- package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +11 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts +10 -0
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +3 -7
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts +8 -0
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-2yb2hfh2.js → recomp-orchestrator-8e82vmvb.js} +6 -4
- package/dist/send-session-notification-6njqewzk.js +20 -0
- package/dist/shared/conflict-detector.d.ts +38 -1
- package/dist/shared/conflict-detector.d.ts.map +1 -1
- package/dist/shared/conflict-fixer.d.ts +18 -1
- package/dist/shared/conflict-fixer.d.ts.map +1 -1
- package/dist/shared/context-limit-provenance.d.ts +3 -0
- package/dist/shared/context-limit-provenance.d.ts.map +1 -0
- package/dist/shared/escalation-bands.d.ts +9 -0
- package/dist/shared/escalation-bands.d.ts.map +1 -0
- package/dist/shared/format-threshold.d.ts +1 -1
- package/dist/shared/models-dev-cache.d.ts +29 -3
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +46 -1
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/storage-permissions.d.ts +5 -0
- package/dist/shared/storage-permissions.d.ts.map +1 -0
- package/dist/{storage-meta-session-td6bwtky.js → storage-meta-session-xt41zbw4.js} +2 -1
- package/dist/tui/compaction-off.d.ts +13 -0
- package/dist/tui/compaction-off.d.ts.map +1 -0
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/compaction-off.d.ts +13 -0
- package/dist/tui-compiled/compaction-off.d.ts.map +1 -0
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/agents/dreamer.ts +68 -0
- package/src/agents/hidden-agent-registrations.ts +331 -0
- package/src/agents/historian.ts +3 -0
- package/src/agents/language-directive.test.ts +138 -0
- package/src/agents/language-directive.ts +121 -0
- package/src/agents/magic-context-prompt.language.test.ts +35 -0
- package/src/agents/magic-context-prompt.test.ts +292 -0
- package/src/agents/magic-context-prompt.ts +182 -0
- package/src/agents/permissions.test.ts +252 -0
- package/src/agents/permissions.ts +186 -0
- package/src/agents/sidekick.ts +1 -0
- package/src/agents/smart-note-compiler.ts +1 -0
- package/src/config/agent-disable.ts +106 -0
- package/src/config/compaction-accessor-guard.test.ts +82 -0
- package/src/config/compaction-backcompat.test.ts +52 -0
- package/src/config/index.test.ts +904 -0
- package/src/config/index.ts +601 -0
- package/src/config/migrate-config-location.test.ts +332 -0
- package/src/config/migrate-config-location.ts +635 -0
- package/src/config/migrate-dreamer-v2.test.ts +287 -0
- package/src/config/migrate-dreamer-v2.ts +336 -0
- package/src/config/migrate-experimental.ts +159 -0
- package/src/config/project-security.test.ts +366 -0
- package/src/config/project-security.ts +588 -0
- package/src/config/prune-config-leaf.test.ts +78 -0
- package/src/config/prune-config-leaf.ts +68 -0
- package/src/config/removed-config-keys.test.ts +116 -0
- package/src/config/schema/agent-overrides.ts +45 -0
- package/src/config/schema/magic-context.test.ts +358 -0
- package/src/config/schema/magic-context.ts +1002 -0
- package/src/config/transform-mode.test.ts +62 -0
- package/src/config/transform-mode.ts +34 -0
- package/src/config/variable.test.ts +323 -0
- package/src/config/variable.ts +192 -0
- package/src/features/magic-context/defaults.ts +1 -0
- package/src/features/magic-context/dreamer/cron.ts +243 -0
- package/src/features/magic-context/dreamer/task-registry.ts +104 -0
- package/src/shared/conflict-detector.test.ts +121 -0
- package/src/shared/conflict-detector.ts +50 -3
- package/src/shared/conflict-fixer.test.ts +309 -0
- package/src/shared/conflict-fixer.ts +82 -5
- package/src/shared/context-limit-provenance.ts +6 -0
- package/src/shared/escalation-bands.test.ts +23 -0
- package/src/shared/escalation-bands.ts +18 -0
- package/src/shared/format-threshold.ts +1 -1
- package/src/shared/models-dev-cache.test.ts +171 -0
- package/src/shared/models-dev-cache.ts +195 -38
- package/src/shared/rpc-server.ts +27 -18
- package/src/shared/rpc-types.ts +46 -1
- package/src/shared/storage-permissions.ts +20 -0
- package/src/tui/compaction-off.test.ts +56 -0
- package/src/tui/compaction-off.ts +29 -0
- package/src/tui/data/context-db.ts +6 -0
- package/src/tui/index.tsx +113 -65
- package/src/tui/slots/sidebar-content.tsx +139 -83
- package/src/tui-compiled/compaction-off.ts +29 -0
- package/src/tui-compiled/data/context-db.ts +6 -0
- package/src/tui-compiled/index.tsx +558 -455
- package/src/tui-compiled/slots/sidebar-content.tsx +237 -161
- package/dist/send-session-notification-tghq9gnh.js +0 -11
- /package/dist/{index-v7fmc1s2.js → index-ygcng9t8.js} +0 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { detectConfigFile, parseJsonc } from "../shared/jsonc-parser";
|
|
4
|
+
import { setOutputReserveConfig } from "../shared/models-dev-cache";
|
|
5
|
+
import { isCompactionEnabled, migrateLegacyAgentEnabledInMemory } from "./agent-disable";
|
|
6
|
+
import {
|
|
7
|
+
cortexKitProjectConfigBasePath,
|
|
8
|
+
cortexKitUserConfigBasePath,
|
|
9
|
+
type LegacyConfigSource,
|
|
10
|
+
resolveLegacyConfigSources,
|
|
11
|
+
resolveLegacyConfigSourcesForHarness,
|
|
12
|
+
} from "./migrate-config-location";
|
|
13
|
+
import { migrateDreamerV2 } from "./migrate-dreamer-v2";
|
|
14
|
+
import { migrateLegacyExperimental } from "./migrate-experimental";
|
|
15
|
+
import {
|
|
16
|
+
constrainProjectThresholdOverrides,
|
|
17
|
+
dropInheritedEmbeddingKeyOnRedirect,
|
|
18
|
+
stripUnsafeProjectConfigFields,
|
|
19
|
+
} from "./project-security";
|
|
20
|
+
import { pruneNestedConfigLeaf } from "./prune-config-leaf";
|
|
21
|
+
import { type MagicContextConfig, MagicContextConfigSchema } from "./schema/magic-context";
|
|
22
|
+
import { resolveTransformMode } from "./transform-mode";
|
|
23
|
+
import { substituteConfigVariables } from "./variable";
|
|
24
|
+
|
|
25
|
+
export interface MagicContextPluginConfig extends MagicContextConfig {
|
|
26
|
+
disabled_hooks?: string[];
|
|
27
|
+
command?: Record<
|
|
28
|
+
string,
|
|
29
|
+
{
|
|
30
|
+
template: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
agent?: string;
|
|
33
|
+
model?: string;
|
|
34
|
+
subtask?: boolean;
|
|
35
|
+
}
|
|
36
|
+
>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Config is read from the shared CortexKit location. The location migrator
|
|
40
|
+
// (migrate-config-location.ts) runs at plugin init and moves legacy per-harness
|
|
41
|
+
// files to the CortexKit path before the loader runs. When the migration could
|
|
42
|
+
// NOT complete (it refuses on an OpenCode-vs-Pi config that differs, or it never
|
|
43
|
+
// ran in this process), the CortexKit base is absent — in that case the loader
|
|
44
|
+
// reads THIS harness's own legacy file as a non-destructive fallback rather than
|
|
45
|
+
// silently using schema defaults (which would re-enable features the user's real
|
|
46
|
+
// config disabled). See resolveLegacyReadFallback.
|
|
47
|
+
function getUserConfigBasePath(): string {
|
|
48
|
+
return cortexKitUserConfigBasePath();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getProjectConfigBasePath(directory: string): string {
|
|
52
|
+
return cortexKitProjectConfigBasePath(directory);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface LegacyReadFallback {
|
|
56
|
+
/** The legacy file we read, or null if no harness-owned legacy file exists. */
|
|
57
|
+
source: LegacyConfigSource | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* First existing legacy source owned by the OpenCode harness, for the read
|
|
62
|
+
* fallback when the CortexKit base is absent. Pi has its own loader with the
|
|
63
|
+
* symmetric Pi-scoped fallback.
|
|
64
|
+
*/
|
|
65
|
+
function resolveLegacyReadFallback(sources: readonly LegacyConfigSource[]): LegacyReadFallback {
|
|
66
|
+
return { source: sources.find((s) => existsSync(s.path)) ?? null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface LoadedConfigFile {
|
|
70
|
+
config: Record<string, unknown>;
|
|
71
|
+
/** Warnings from {env:} / {file:} substitution, with config-path prefix applied. */
|
|
72
|
+
warnings: string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type LoadOutcome =
|
|
76
|
+
| "ok"
|
|
77
|
+
| "project-file-parse-error"
|
|
78
|
+
| "project-file-io-error"
|
|
79
|
+
| "legacy-config-unmigrated"
|
|
80
|
+
| "schema-recovery"
|
|
81
|
+
| "substitution-failure";
|
|
82
|
+
|
|
83
|
+
export interface LoadResultDetailed {
|
|
84
|
+
config: MagicContextPluginConfig & { configWarnings?: string[] };
|
|
85
|
+
loadOutcome: LoadOutcome;
|
|
86
|
+
sources: {
|
|
87
|
+
userConfig: LoadOutcome;
|
|
88
|
+
projectConfig: LoadOutcome;
|
|
89
|
+
};
|
|
90
|
+
substitutionFailures: Array<{ keyPath: string; source: "user" | "project"; message: string }>;
|
|
91
|
+
recoveredTopLevelKeys: string[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface LoadedConfigFileDetailed extends LoadedConfigFile {
|
|
95
|
+
outcome: LoadOutcome;
|
|
96
|
+
source: "user" | "project";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function loadConfigFileDetailed(
|
|
100
|
+
configPath: string,
|
|
101
|
+
source: "user" | "project",
|
|
102
|
+
): LoadedConfigFileDetailed | null {
|
|
103
|
+
if (!existsSync(configPath)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let rawText: string;
|
|
108
|
+
try {
|
|
109
|
+
rawText = readFileSync(configPath, "utf-8");
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return {
|
|
112
|
+
config: {},
|
|
113
|
+
warnings: [
|
|
114
|
+
`${configPath}: failed to read config: ${error instanceof Error ? error.message : String(error)}`,
|
|
115
|
+
],
|
|
116
|
+
outcome: "project-file-io-error",
|
|
117
|
+
source,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const substituted = substituteConfigVariables({
|
|
123
|
+
text: rawText,
|
|
124
|
+
configPath,
|
|
125
|
+
isProjectConfig: source === "project",
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
config: parseJsonc<Record<string, unknown>>(substituted.text),
|
|
129
|
+
warnings: substituted.warnings.map((w) => `${configPath}: ${w}`),
|
|
130
|
+
outcome: substituted.warnings.length > 0 ? "substitution-failure" : "ok",
|
|
131
|
+
source,
|
|
132
|
+
};
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
config: {},
|
|
136
|
+
warnings: [
|
|
137
|
+
`${configPath}: failed to load config: ${error instanceof Error ? error.message : String(error)}`,
|
|
138
|
+
],
|
|
139
|
+
outcome: "project-file-parse-error",
|
|
140
|
+
source,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Deep-merge two raw JSON objects. Both inputs must come from BEFORE Zod
|
|
147
|
+
* parsing — otherwise Zod-filled defaults appear as if they were explicit
|
|
148
|
+
* overrides and clobber genuine values from the other source.
|
|
149
|
+
*
|
|
150
|
+
* Plain object values merge recursively. Arrays, primitives, and `null` are
|
|
151
|
+
* replaced atomically (override wins). This matches typical config-merge
|
|
152
|
+
* semantics: arrays like `disabled_hooks` should be set whole, not interleaved
|
|
153
|
+
* element-wise.
|
|
154
|
+
*
|
|
155
|
+
* `disabled_hooks` is the one exception: we union-merge it below so user
|
|
156
|
+
* and project can both contribute hook IDs without one silently losing the
|
|
157
|
+
* other's entries.
|
|
158
|
+
*/
|
|
159
|
+
function deepMergeRawConfig(
|
|
160
|
+
base: Record<string, unknown>,
|
|
161
|
+
override: Record<string, unknown>,
|
|
162
|
+
): Record<string, unknown> {
|
|
163
|
+
const result: Record<string, unknown> = { ...base };
|
|
164
|
+
for (const key of Object.keys(override)) {
|
|
165
|
+
const baseVal = base[key];
|
|
166
|
+
const overrideVal = override[key];
|
|
167
|
+
if (
|
|
168
|
+
baseVal !== null &&
|
|
169
|
+
typeof baseVal === "object" &&
|
|
170
|
+
!Array.isArray(baseVal) &&
|
|
171
|
+
overrideVal !== null &&
|
|
172
|
+
typeof overrideVal === "object" &&
|
|
173
|
+
!Array.isArray(overrideVal)
|
|
174
|
+
) {
|
|
175
|
+
result[key] = deepMergeRawConfig(
|
|
176
|
+
baseVal as Record<string, unknown>,
|
|
177
|
+
overrideVal as Record<string, unknown>,
|
|
178
|
+
);
|
|
179
|
+
} else if (
|
|
180
|
+
key === "disabled_hooks" &&
|
|
181
|
+
Array.isArray(baseVal) &&
|
|
182
|
+
Array.isArray(overrideVal)
|
|
183
|
+
) {
|
|
184
|
+
// Union-merge so user + project can both disable hooks without
|
|
185
|
+
// one source erasing the other's entries.
|
|
186
|
+
result[key] = [...new Set([...baseVal, ...overrideVal])];
|
|
187
|
+
} else {
|
|
188
|
+
result[key] = overrideVal;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Render a config value for a warning message in a way that never leaks resolved
|
|
196
|
+
* secrets from `{env:API_KEY}` / `{file:...}` substitution.
|
|
197
|
+
*
|
|
198
|
+
* Strings, numbers, booleans, and nulls are shown as type-plus-length so the
|
|
199
|
+
* user can still diagnose the problem ("string, 48 chars", "number 200001") but
|
|
200
|
+
* never see the resolved content. Objects and arrays are shown as their
|
|
201
|
+
* structural shape only. `undefined` / missing values are reported as
|
|
202
|
+
* `<missing>`.
|
|
203
|
+
*/
|
|
204
|
+
function redactConfigValue(value: unknown): string {
|
|
205
|
+
if (value === undefined) return "<missing>";
|
|
206
|
+
if (value === null) return "null";
|
|
207
|
+
if (typeof value === "string")
|
|
208
|
+
return `string, ${value.length} char${value.length === 1 ? "" : "s"}`;
|
|
209
|
+
if (typeof value === "number") return `number ${value}`;
|
|
210
|
+
if (typeof value === "boolean") return `boolean ${value}`;
|
|
211
|
+
if (Array.isArray(value)) return `array, ${value.length} item${value.length === 1 ? "" : "s"}`;
|
|
212
|
+
if (typeof value === "object") {
|
|
213
|
+
const keys = Object.keys(value as Record<string, unknown>);
|
|
214
|
+
return `object with keys [${keys.join(", ")}]`;
|
|
215
|
+
}
|
|
216
|
+
return typeof value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parsePluginConfig(
|
|
220
|
+
rawConfig: Record<string, unknown>,
|
|
221
|
+
recoveredTopLevelKeys: string[] = [],
|
|
222
|
+
): MagicContextPluginConfig & { configWarnings?: string[] } {
|
|
223
|
+
// Pre-Zod shim: reshape legacy experimental.* graduated keys so the user's
|
|
224
|
+
// opt-in/out state survives upgrades even when they never run `doctor`.
|
|
225
|
+
const preMigrationWarnings: string[] = [];
|
|
226
|
+
const migratedExperimental = migrateLegacyExperimental(rawConfig, preMigrationWarnings);
|
|
227
|
+
// Dreamer v2: convert the legacy v1 dreamer shape (window schedule, tasks
|
|
228
|
+
// array, user_memories/pin_key_files blocks) into the per-task `tasks` record.
|
|
229
|
+
// Runs AFTER migrate-experimental so experimental.user_memories (already
|
|
230
|
+
// relocated to dreamer.user_memories above) is folded into the v2 tasks here.
|
|
231
|
+
const migratedDreamer = migrateDreamerV2(migratedExperimental, preMigrationWarnings);
|
|
232
|
+
const migrated = migrateLegacyAgentEnabledInMemory(migratedDreamer, preMigrationWarnings);
|
|
233
|
+
const parsed = MagicContextConfigSchema.safeParse(migrated);
|
|
234
|
+
const disabledHooks = Array.isArray(rawConfig.disabled_hooks)
|
|
235
|
+
? rawConfig.disabled_hooks.filter((value): value is string => typeof value === "string")
|
|
236
|
+
: undefined;
|
|
237
|
+
const command =
|
|
238
|
+
typeof rawConfig.command === "object" && rawConfig.command !== null
|
|
239
|
+
? (rawConfig.command as MagicContextPluginConfig["command"])
|
|
240
|
+
: undefined;
|
|
241
|
+
|
|
242
|
+
if (parsed.success) {
|
|
243
|
+
return {
|
|
244
|
+
...parsed.data,
|
|
245
|
+
disabled_hooks: disabledHooks,
|
|
246
|
+
command,
|
|
247
|
+
...(preMigrationWarnings.length > 0 ? { configWarnings: preMigrationWarnings } : {}),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Full parse failed — recover field-by-field using defaults for invalid fields.
|
|
252
|
+
// Agent configs (historian, dreamer, sidekick) are dropped on error rather than defaulted
|
|
253
|
+
// because wrong model config could run expensive models or fail silently.
|
|
254
|
+
const defaults = MagicContextConfigSchema.parse({});
|
|
255
|
+
const warnings: string[] = [];
|
|
256
|
+
|
|
257
|
+
// Build a patched copy of rawConfig, replacing invalid fields with undefined
|
|
258
|
+
// so Zod fills in defaults on the second parse.
|
|
259
|
+
const errorPaths = new Set<string>();
|
|
260
|
+
// Collect any custom Zod messages per top-level key so a field with an
|
|
261
|
+
// explanatory `.max(..., "why")` / `.refine(..., "why")` message surfaces the
|
|
262
|
+
// reason to the user instead of a bare "invalid value" (e.g. issue #111's
|
|
263
|
+
// execute_threshold cache-safety explanation). Only non-default Zod messages
|
|
264
|
+
// are kept — the generic "Too big"/"Invalid input" boilerplate adds nothing.
|
|
265
|
+
const customMessagesByKey = new Map<string, string>();
|
|
266
|
+
// Per top-level key, the set of FULL error paths (e.g. ["memory","auto_search"]).
|
|
267
|
+
// Used to prune only the invalid nested leaf instead of the whole block.
|
|
268
|
+
const issuePathsByKey = new Map<string, PropertyKey[][]>();
|
|
269
|
+
const GENERIC_ZOD_PREFIXES = ["Too big", "Too small", "Invalid input", "Invalid", "Expected"];
|
|
270
|
+
for (const issue of parsed.error.issues) {
|
|
271
|
+
const topKey = issue.path[0];
|
|
272
|
+
if (topKey !== undefined) {
|
|
273
|
+
const key = String(topKey);
|
|
274
|
+
errorPaths.add(key);
|
|
275
|
+
const paths = issuePathsByKey.get(key) ?? [];
|
|
276
|
+
paths.push([...issue.path]);
|
|
277
|
+
issuePathsByKey.set(key, paths);
|
|
278
|
+
const msg = issue.message;
|
|
279
|
+
if (msg && !GENERIC_ZOD_PREFIXES.some((p) => msg.startsWith(p))) {
|
|
280
|
+
if (!customMessagesByKey.has(key)) {
|
|
281
|
+
customMessagesByKey.set(key, msg);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const patched: Record<string, unknown> = { ...rawConfig };
|
|
288
|
+
for (const key of errorPaths) {
|
|
289
|
+
recoveredTopLevelKeys.push(key);
|
|
290
|
+
const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
|
|
291
|
+
if (isAgentConfig) {
|
|
292
|
+
// Drop agent configs entirely on error — don't default them
|
|
293
|
+
delete patched[key];
|
|
294
|
+
warnings.push(
|
|
295
|
+
`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`,
|
|
296
|
+
);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// For object-valued keys (e.g. `memory`), prune ONLY the invalid nested
|
|
301
|
+
// leaves and keep valid siblings, so one bad nested field doesn't wipe the
|
|
302
|
+
// whole block — which would silently drop already-migrated graduated keys
|
|
303
|
+
// like memory.auto_search / memory.git_commit_indexing. Falls back to
|
|
304
|
+
// whole-key deletion when the issue is at the key itself or the value
|
|
305
|
+
// isn't a prunable object.
|
|
306
|
+
const issuePaths = issuePathsByKey.get(key) ?? [];
|
|
307
|
+
const rawValue = rawConfig[key];
|
|
308
|
+
const allNested =
|
|
309
|
+
issuePaths.length > 0 &&
|
|
310
|
+
issuePaths.every((p) => p.length >= 2) &&
|
|
311
|
+
typeof rawValue === "object" &&
|
|
312
|
+
rawValue !== null &&
|
|
313
|
+
!Array.isArray(rawValue);
|
|
314
|
+
if (allNested) {
|
|
315
|
+
let prunedBlock: Record<string, unknown> = {
|
|
316
|
+
...(rawValue as Record<string, unknown>),
|
|
317
|
+
};
|
|
318
|
+
const prunedLeaves: string[] = [];
|
|
319
|
+
for (const p of issuePaths) {
|
|
320
|
+
// p is the full Zod issue path ([key, ...nested]); prune the
|
|
321
|
+
// DEEPEST invalid leaf, not just the first child (p[1]), so a
|
|
322
|
+
// 3-level path like memory.git_commit_indexing.since_days drops
|
|
323
|
+
// only `since_days` and keeps a sibling `enabled: false`.
|
|
324
|
+
const relative = p.slice(1);
|
|
325
|
+
const result = pruneNestedConfigLeaf(prunedBlock, relative);
|
|
326
|
+
if (result) {
|
|
327
|
+
prunedBlock = result.block;
|
|
328
|
+
prunedLeaves.push(result.removed);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
patched[key] = prunedBlock;
|
|
332
|
+
const reason = customMessagesByKey.get(key);
|
|
333
|
+
warnings.push(
|
|
334
|
+
`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.${reason ? ` ${reason}` : ""}`,
|
|
335
|
+
);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Use Zod default for this field.
|
|
340
|
+
// Intentional: redactConfigValue reports type+length, never the
|
|
341
|
+
// resolved value itself, because `{env:...}` / `{file:...}`
|
|
342
|
+
// substitution may have already expanded secrets into rawConfig.
|
|
343
|
+
delete patched[key];
|
|
344
|
+
const defaultVal = (defaults as unknown as Record<string, unknown>)[key];
|
|
345
|
+
const reason = customMessagesByKey.get(key);
|
|
346
|
+
warnings.push(
|
|
347
|
+
`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Re-run migration on the field-recovered patched config so legacy
|
|
352
|
+
// experimental + dreamer-v1 blocks still migrate on the recovery path.
|
|
353
|
+
const retryMigrated = migrateLegacyAgentEnabledInMemory(
|
|
354
|
+
migrateDreamerV2(
|
|
355
|
+
migrateLegacyExperimental(patched, preMigrationWarnings),
|
|
356
|
+
preMigrationWarnings,
|
|
357
|
+
),
|
|
358
|
+
preMigrationWarnings,
|
|
359
|
+
);
|
|
360
|
+
const retryParsed = MagicContextConfigSchema.safeParse(retryMigrated);
|
|
361
|
+
if (retryParsed.success) {
|
|
362
|
+
return {
|
|
363
|
+
...retryParsed.data,
|
|
364
|
+
disabled_hooks: disabledHooks,
|
|
365
|
+
command,
|
|
366
|
+
configWarnings: [...preMigrationWarnings, ...warnings],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// If even the patched version fails (shouldn't happen), fall back to full defaults
|
|
371
|
+
// but keep enabled:true — the user intended to use the plugin.
|
|
372
|
+
warnings.push("Config recovery failed, using all defaults.");
|
|
373
|
+
return {
|
|
374
|
+
...defaults,
|
|
375
|
+
disabled_hooks: disabledHooks,
|
|
376
|
+
command,
|
|
377
|
+
configWarnings: [...preMigrationWarnings, ...warnings],
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function loadPluginConfig(
|
|
382
|
+
directory: string,
|
|
383
|
+
): MagicContextPluginConfig & { configWarnings?: string[] } {
|
|
384
|
+
// Delegate to the detailed loader so there is exactly ONE config-resolution
|
|
385
|
+
// path. The detailed variant owns the read-legacy-on-conflict fallback (when
|
|
386
|
+
// the shared CortexKit base is absent, read THIS harness's own legacy config
|
|
387
|
+
// instead of falling to schema defaults), the project-config hardening, and
|
|
388
|
+
// the embedding-redirect guard. Having a second hand-maintained copy here is
|
|
389
|
+
// how the read-legacy fallback silently missed the runtime init path
|
|
390
|
+
// (index.ts calls loadPluginConfig) while only the embedding bootstrap got
|
|
391
|
+
// it — a config-drop bug. The extra detailed fields (outcome, sources,
|
|
392
|
+
// substitutionFailures) are simply dropped for callers that only need the
|
|
393
|
+
// config + warnings.
|
|
394
|
+
return loadPluginConfigDetailed(directory).config;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function hasUserTierSubcConfig(config: Record<string, unknown> | undefined): boolean {
|
|
398
|
+
const subc = config?.subc;
|
|
399
|
+
if (typeof subc !== "object" || subc === null || Array.isArray(subc)) return false;
|
|
400
|
+
const connectionFile = (subc as Record<string, unknown>).connection_file;
|
|
401
|
+
return typeof connectionFile === "string" && connectionFile.trim().length > 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function collectEmptyStringPaths(value: unknown, prefix = ""): string[] {
|
|
405
|
+
if (typeof value === "string") {
|
|
406
|
+
return value === "" && prefix ? [prefix] : [];
|
|
407
|
+
}
|
|
408
|
+
if (Array.isArray(value) || value === null || typeof value !== "object") {
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const paths: string[] = [];
|
|
413
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
414
|
+
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
|
415
|
+
paths.push(...collectEmptyStringPaths(child, nextPrefix));
|
|
416
|
+
}
|
|
417
|
+
return paths;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function bindSubstitutionFailures(
|
|
421
|
+
loaded: LoadedConfigFileDetailed | null,
|
|
422
|
+
): Array<{ keyPath: string; source: "user" | "project"; message: string }> {
|
|
423
|
+
if (!loaded || loaded.warnings.length === 0 || loaded.outcome !== "substitution-failure") {
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const emptyPaths = collectEmptyStringPaths(loaded.config);
|
|
428
|
+
return loaded.warnings.map((message) => {
|
|
429
|
+
const matchedPath = emptyPaths.find((path) => {
|
|
430
|
+
const tail = path.split(".").at(-1) ?? path;
|
|
431
|
+
return message.includes(path) || message.toLowerCase().includes(tail.toLowerCase());
|
|
432
|
+
});
|
|
433
|
+
return { keyPath: matchedPath ?? "<unknown>", source: loaded.source, message };
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function combinedOutcome(args: {
|
|
438
|
+
sources: LoadResultDetailed["sources"];
|
|
439
|
+
substitutionFailures: LoadResultDetailed["substitutionFailures"];
|
|
440
|
+
recoveredTopLevelKeys: string[];
|
|
441
|
+
}): LoadOutcome {
|
|
442
|
+
const sourceOutcomes = Object.values(args.sources);
|
|
443
|
+
if (sourceOutcomes.includes("project-file-parse-error")) return "project-file-parse-error";
|
|
444
|
+
if (sourceOutcomes.includes("project-file-io-error")) return "project-file-io-error";
|
|
445
|
+
if (sourceOutcomes.includes("legacy-config-unmigrated")) return "legacy-config-unmigrated";
|
|
446
|
+
if (args.recoveredTopLevelKeys.length > 0) return "schema-recovery";
|
|
447
|
+
if (args.substitutionFailures.length > 0) return "substitution-failure";
|
|
448
|
+
return "ok";
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function loadPluginConfigDetailed(directory: string): LoadResultDetailed {
|
|
452
|
+
const userDetected = detectConfigFile(getUserConfigBasePath());
|
|
453
|
+
const projectDetected = detectConfigFile(getProjectConfigBasePath(directory));
|
|
454
|
+
// Both-harness sources drive the GC-suppression signal; this-harness sources
|
|
455
|
+
// (OpenCode) drive the non-destructive read fallback when the base is absent.
|
|
456
|
+
const legacySources = resolveLegacyConfigSources(directory);
|
|
457
|
+
const harnessLegacy = resolveLegacyConfigSourcesForHarness(directory, "opencode");
|
|
458
|
+
|
|
459
|
+
// When the CortexKit base is absent (migration refused on a differing
|
|
460
|
+
// OpenCode/Pi pair, or hasn't run), read THIS harness's own legacy file
|
|
461
|
+
// instead of falling to schema defaults that would silently re-enable
|
|
462
|
+
// features the user disabled.
|
|
463
|
+
const userLegacyFallback =
|
|
464
|
+
userDetected.format === "none"
|
|
465
|
+
? resolveLegacyReadFallback(harnessLegacy.user)
|
|
466
|
+
: { source: null };
|
|
467
|
+
const projectLegacyFallback =
|
|
468
|
+
projectDetected.format === "none"
|
|
469
|
+
? resolveLegacyReadFallback(harnessLegacy.project)
|
|
470
|
+
: { source: null };
|
|
471
|
+
|
|
472
|
+
// "Unmigrated" (→ untrusted, GC suppressed) ONLY when the base is absent,
|
|
473
|
+
// some legacy config exists, AND we did NOT read this harness's own legacy.
|
|
474
|
+
// If we read our own legacy the config is real → trusted. If only the OTHER
|
|
475
|
+
// harness's legacy exists we fell to defaults → keep GC suppressed so a
|
|
476
|
+
// default-config start can't reap the other harness's embedding vectors.
|
|
477
|
+
const legacyUserUnmigrated =
|
|
478
|
+
userDetected.format === "none" &&
|
|
479
|
+
!userLegacyFallback.source &&
|
|
480
|
+
legacySources.user.some((source) => existsSync(source.path));
|
|
481
|
+
const legacyProjectUnmigrated =
|
|
482
|
+
projectDetected.format === "none" &&
|
|
483
|
+
!projectLegacyFallback.source &&
|
|
484
|
+
legacySources.project.some((source) => existsSync(source.path));
|
|
485
|
+
|
|
486
|
+
const userLoaded =
|
|
487
|
+
userDetected.format !== "none"
|
|
488
|
+
? loadConfigFileDetailed(userDetected.path, "user")
|
|
489
|
+
: userLegacyFallback.source
|
|
490
|
+
? loadConfigFileDetailed(userLegacyFallback.source.path, "user")
|
|
491
|
+
: null;
|
|
492
|
+
const projectLoaded =
|
|
493
|
+
projectDetected.format !== "none"
|
|
494
|
+
? loadConfigFileDetailed(projectDetected.path, "project")
|
|
495
|
+
: projectLegacyFallback.source
|
|
496
|
+
? loadConfigFileDetailed(projectLegacyFallback.source.path, "project")
|
|
497
|
+
: null;
|
|
498
|
+
|
|
499
|
+
const allWarnings: string[] = [];
|
|
500
|
+
let mergedRaw: Record<string, unknown> = {};
|
|
501
|
+
// Threshold trust boundary is relative to the USER/default effective config:
|
|
502
|
+
// a cloned repo may delay compaction, but it may not lower thresholds in a
|
|
503
|
+
// way that forces extra historian work on the user's account.
|
|
504
|
+
const trustedBaseConfig = parsePluginConfig(userLoaded?.config ?? {});
|
|
505
|
+
|
|
506
|
+
if (userLegacyFallback.source) {
|
|
507
|
+
allWarnings.push(
|
|
508
|
+
`[user config] reading legacy config from ${userLegacyFallback.source.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`,
|
|
509
|
+
);
|
|
510
|
+
} else if (legacyUserUnmigrated) {
|
|
511
|
+
allWarnings.push(
|
|
512
|
+
"[user config] legacy Magic Context config exists but the shared CortexKit config is absent; embedding registration is paused until config migration completes.",
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (projectLegacyFallback.source) {
|
|
517
|
+
allWarnings.push(
|
|
518
|
+
`[project config] reading legacy config from ${projectLegacyFallback.source.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`,
|
|
519
|
+
);
|
|
520
|
+
} else if (legacyProjectUnmigrated) {
|
|
521
|
+
allWarnings.push(
|
|
522
|
+
"[project config] legacy Magic Context config exists but the shared CortexKit config is absent; embedding registration is paused until config migration completes.",
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (userLoaded) {
|
|
527
|
+
allWarnings.push(...userLoaded.warnings.map((w) => `[user config] ${w}`));
|
|
528
|
+
mergedRaw = deepMergeRawConfig(mergedRaw, userLoaded.config);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (projectLoaded) {
|
|
532
|
+
allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
|
|
533
|
+
const projectRaw = { ...projectLoaded.config };
|
|
534
|
+
for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
|
|
535
|
+
allWarnings.push(`[project config] ${warning}`);
|
|
536
|
+
}
|
|
537
|
+
mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
|
|
538
|
+
for (const warning of dropInheritedEmbeddingKeyOnRedirect(
|
|
539
|
+
projectRaw,
|
|
540
|
+
mergedRaw,
|
|
541
|
+
userLoaded?.config,
|
|
542
|
+
)) {
|
|
543
|
+
allWarnings.push(`[project config] ${warning}`);
|
|
544
|
+
}
|
|
545
|
+
for (const warning of constrainProjectThresholdOverrides({
|
|
546
|
+
mergedRaw,
|
|
547
|
+
projectRaw,
|
|
548
|
+
trustedBaseConfig,
|
|
549
|
+
})) {
|
|
550
|
+
allWarnings.push(`[project config] ${warning}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const recoveredTopLevelKeys: string[] = [];
|
|
555
|
+
const config = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
|
|
556
|
+
setOutputReserveConfig(config.output_reserve);
|
|
557
|
+
if (config.configWarnings?.length) {
|
|
558
|
+
allWarnings.push(
|
|
559
|
+
...config.configWarnings.map((w) => {
|
|
560
|
+
if (userLoaded && projectLoaded) return `[config] ${w}`;
|
|
561
|
+
if (userLoaded) return `[user config] ${w}`;
|
|
562
|
+
return `[project config] ${w}`;
|
|
563
|
+
}),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const resolvedTransformMode = resolveTransformMode({
|
|
568
|
+
configured: config.transform_mode,
|
|
569
|
+
userTierHasSubc: hasUserTierSubcConfig(userLoaded?.config),
|
|
570
|
+
compactionEnabled: isCompactionEnabled(config),
|
|
571
|
+
});
|
|
572
|
+
config.transform_mode = resolvedTransformMode.mode;
|
|
573
|
+
allWarnings.push(...resolvedTransformMode.warnings.map((warning) => `[config] ${warning}`));
|
|
574
|
+
|
|
575
|
+
if (allWarnings.length > 0) {
|
|
576
|
+
config.configWarnings = allWarnings;
|
|
577
|
+
} else if ("configWarnings" in config) {
|
|
578
|
+
config.configWarnings = undefined;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const substitutionFailures = [
|
|
582
|
+
...bindSubstitutionFailures(userLoaded),
|
|
583
|
+
...bindSubstitutionFailures(projectLoaded),
|
|
584
|
+
];
|
|
585
|
+
const sources = {
|
|
586
|
+
userConfig:
|
|
587
|
+
userLoaded?.outcome ??
|
|
588
|
+
(legacyUserUnmigrated ? "legacy-config-unmigrated" : ("ok" as LoadOutcome)),
|
|
589
|
+
projectConfig:
|
|
590
|
+
projectLoaded?.outcome ??
|
|
591
|
+
(legacyProjectUnmigrated ? "legacy-config-unmigrated" : ("ok" as LoadOutcome)),
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
config,
|
|
596
|
+
loadOutcome: combinedOutcome({ sources, substitutionFailures, recoveredTopLevelKeys }),
|
|
597
|
+
sources,
|
|
598
|
+
substitutionFailures,
|
|
599
|
+
recoveredTopLevelKeys,
|
|
600
|
+
};
|
|
601
|
+
}
|