@cortexkit/opencode-magic-context 0.33.0 → 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 +144 -24
- 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-bwa99a04.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 +106 -0
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts +0 -2
- 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/task-registry.d.ts +0 -1
- package/dist/features/magic-context/dreamer/task-registry.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/index.d.ts +2 -2
- package/dist/features/magic-context/git-commits/index.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +0 -2
- 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 +0 -13
- 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 +1 -2
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/memory/memory-migration.d.ts +0 -6
- 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 +0 -1
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +1 -6
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/verification-paths.d.ts +0 -1
- package/dist/features/magic-context/memory/verification-paths.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/mural/storage-mural.d.ts +0 -1
- package/dist/features/magic-context/mural/storage-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 +16 -2
- 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/capabilities.d.ts +0 -2
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +0 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/types.d.ts +0 -16
- package/dist/features/magic-context/smart-notes/types.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 -5
- 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/features/magic-context/user-memory/storage-user-memory.d.ts +0 -2
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/types.d.ts +0 -3
- package/dist/hooks/auto-update-checker/types.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 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.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-marker-manager.d.ts +49 -0
- package/dist/hooks/magic-context/compaction-marker-manager.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-parser.d.ts +12 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
- 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 +0 -12
- 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/historian-prompt.generated.d.ts +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +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 +18 -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/sentinel.d.ts +39 -0
- package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-drop-target.d.ts +19 -0
- package/dist/hooks/magic-context/tool-drop-target.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-kxz4tn6m.js → index-56yjaeb2.js} +56 -4
- package/dist/{index-bmkea2dq.js → index-b563sga5.js} +40 -287
- package/dist/{index-y1c9tcm3.js → index-cn960ffd.js} +101 -20
- package/dist/{index-k5y60ymm.js → index-dtas91s5.js} +27 -6
- package/dist/index-fcyvznch.js +279 -0
- package/dist/{index-63dq2g4d.js → index-ry8rh8cv.js} +138 -24
- package/dist/{index-h86twgsp.js → index-s0mdn690.js} +2113 -1067
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1356 -627
- package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +19 -8
- 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-yh7pepvx.js → recomp-orchestrator-8e82vmvb.js} +6 -5
- 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/sqlite.d.ts +9 -4
- package/dist/shared/sqlite.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-1phy0b1z.js → storage-meta-session-xt41zbw4.js} +2 -1
- package/dist/{task-config-dn9k9szr.js → task-config-5nx3qfz8.js} +1 -2
- 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 +8 -3
- 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 +251 -1
- package/src/shared/conflict-detector.ts +84 -5
- 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/sqlite.ts +13 -33
- 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/config/schema.d.ts +0 -3
- package/dist/config/schema.d.ts.map +0 -1
- package/dist/features/magic-context/plugin-messages.d.ts +0 -75
- package/dist/features/magic-context/plugin-messages.d.ts.map +0 -1
- package/dist/hooks/magic-context/compartment-runner-state-xml.d.ts +0 -21
- package/dist/hooks/magic-context/compartment-runner-state-xml.d.ts.map +0 -1
- package/dist/index-anhrfygk.js +0 -57
- package/dist/send-session-notification-tghq9gnh.js +0 -11
- package/dist/shared/opencode-compaction-detector.d.ts +0 -2
- package/dist/shared/opencode-compaction-detector.d.ts.map +0 -1
- package/dist/shared/transcript-opencode.d.ts +0 -71
- package/dist/shared/transcript-opencode.d.ts.map +0 -1
- package/dist/tools/index.d.ts +0 -6
- package/dist/tools/index.d.ts.map +0 -1
- package/src/shared/opencode-compaction-detector.test.ts +0 -239
- package/src/shared/opencode-compaction-detector.ts +0 -81
- package/src/shared/transcript-opencode.ts +0 -302
- /package/dist/{index-v7fmc1s2.js → index-ygcng9t8.js} +0 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
openSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
statSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Config-LOCATION migration: move Magic Context config from the per-harness
|
|
18
|
+
* legacy locations to one shared CortexKit location, mirroring AFT's proven
|
|
19
|
+
* move-and-marker design (NOT copy-in-place, which is a silent stale-edit trap).
|
|
20
|
+
*
|
|
21
|
+
* Legacy (read by old builds):
|
|
22
|
+
* user: ~/.config/opencode/magic-context.{jsonc,json}
|
|
23
|
+
* ~/.pi/agent/magic-context.{jsonc,json}
|
|
24
|
+
* project: <root>/magic-context.{jsonc,json} (bare root)
|
|
25
|
+
* <root>/.opencode/magic-context.{jsonc,json}
|
|
26
|
+
* <root>/.pi/magic-context.{jsonc,json}
|
|
27
|
+
*
|
|
28
|
+
* Target (the only thing new builds read — HARD CUTOVER, harness-agnostic):
|
|
29
|
+
* user: ~/.config/cortexkit/magic-context.jsonc
|
|
30
|
+
* project: <root>/.cortexkit/magic-context.jsonc
|
|
31
|
+
*
|
|
32
|
+
* The migrator runs at plugin init before the loader. Idempotency comes from
|
|
33
|
+
* the legacy file being renamed away (not from a sentinel): once a source is
|
|
34
|
+
* moved aside to `<name>.MOVED_READPLEASE`, later runs find no legacy source
|
|
35
|
+
* and no-op. The marker is a pure human breadcrumb — the loader never reads it.
|
|
36
|
+
*
|
|
37
|
+
* NB: this is config-LOCATION migration. It is unrelated to the schema/key
|
|
38
|
+
* migrations (migrate-dreamer-v2, migrate-experimental) that rewrite keys
|
|
39
|
+
* INSIDE an already-located config file.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export interface LegacyConfigSource {
|
|
43
|
+
path: string;
|
|
44
|
+
label: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ConfigMigrationLogger {
|
|
48
|
+
warn?: (msg: string) => void;
|
|
49
|
+
info?: (msg: string) => void;
|
|
50
|
+
log?: (msg: string) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ConfigFileMigrationOptions {
|
|
54
|
+
scope: "user" | "project";
|
|
55
|
+
targetPath: string;
|
|
56
|
+
legacySources: readonly LegacyConfigSource[];
|
|
57
|
+
logger?: ConfigMigrationLogger;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ConfigFileMigrationResult {
|
|
61
|
+
migrated: boolean;
|
|
62
|
+
conflict: boolean;
|
|
63
|
+
sourcePath?: string;
|
|
64
|
+
targetPath: string;
|
|
65
|
+
warnings: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const CONFIG_FILE_BASENAME = "magic-context";
|
|
69
|
+
const MOVED_MARKER_SUFFIX = ".MOVED_READPLEASE";
|
|
70
|
+
|
|
71
|
+
// ── Path resolution ──────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
function homeDir(): string {
|
|
74
|
+
if (process.platform === "win32") {
|
|
75
|
+
return process.env.USERPROFILE || process.env.HOME || homedir();
|
|
76
|
+
}
|
|
77
|
+
return process.env.HOME || homedir();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function configHome(): string {
|
|
81
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
82
|
+
if (xdg && isAbsolute(xdg)) return xdg;
|
|
83
|
+
return join(homeDir(), ".config");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** `~/.config/cortexkit/magic-context` (no extension — for detectConfigFile). */
|
|
87
|
+
export function cortexKitUserConfigBasePath(): string {
|
|
88
|
+
return join(configHome(), "cortexkit", CONFIG_FILE_BASENAME);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `<root>/.cortexkit/magic-context` (no extension — for detectConfigFile). */
|
|
92
|
+
export function cortexKitProjectConfigBasePath(directory: string): string {
|
|
93
|
+
return join(directory, ".cortexkit", CONFIG_FILE_BASENAME);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The migration target: always normalized to `.jsonc`. */
|
|
97
|
+
export function resolveCortexKitUserConfigPath(): string {
|
|
98
|
+
return `${cortexKitUserConfigBasePath()}.jsonc`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The migration target: always normalized to `.jsonc`. */
|
|
102
|
+
export function resolveCortexKitProjectConfigPath(directory: string): string {
|
|
103
|
+
return `${cortexKitProjectConfigBasePath(directory)}.jsonc`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function legacySourcesForBase(basePath: string, label: string): LegacyConfigSource[] {
|
|
107
|
+
return [
|
|
108
|
+
{ path: `${basePath}.jsonc`, label: `${label} magic-context.jsonc` },
|
|
109
|
+
{ path: `${basePath}.json`, label: `${label} magic-context.json` },
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Every user-scope config path the migrator knows: the CortexKit user target
|
|
115
|
+
* plus the user legacy sources. A project-scope migration must NEVER move any of
|
|
116
|
+
* these (they are user-scope files, not project files).
|
|
117
|
+
*/
|
|
118
|
+
function userScopeConfigPaths(): Set<string> {
|
|
119
|
+
return new Set<string>([
|
|
120
|
+
// The CortexKit user target, both extensions: the bare-root project
|
|
121
|
+
// source produces `.jsonc` AND `.json`, so exclude both or a stray
|
|
122
|
+
// `~/.config/cortexkit/magic-context.json` would still be eaten.
|
|
123
|
+
`${cortexKitUserConfigBasePath()}.jsonc`,
|
|
124
|
+
`${cortexKitUserConfigBasePath()}.json`,
|
|
125
|
+
join(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.jsonc`),
|
|
126
|
+
join(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.json`),
|
|
127
|
+
join(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.jsonc`),
|
|
128
|
+
join(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.json`),
|
|
129
|
+
]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The legacy config locations to migrate FROM, by scope. Each base produces a
|
|
134
|
+
* `.jsonc` and a `.json` candidate; whichever exists migrates, target is always
|
|
135
|
+
* `.jsonc`. The bare-root project source (`<root>/magic-context.*`) is unique to
|
|
136
|
+
* Magic Context (AFT never had it) — omitting it would orphan repo-root configs.
|
|
137
|
+
*
|
|
138
|
+
* Project sources are filtered against the user-scope path set: when a session's
|
|
139
|
+
* project directory IS the user config home (e.g. opencode opened in
|
|
140
|
+
* `~/.config/cortexkit`), the bare-root project source `<root>/magic-context.jsonc`
|
|
141
|
+
* resolves to the USER config path. Without this guard the project migration
|
|
142
|
+
* would "migrate" the user's own config into `<root>/.cortexkit/` and rename the
|
|
143
|
+
* original aside, leaving the user on schema defaults (the config-eats-itself
|
|
144
|
+
* bug). A project migration must never touch a user-scope file.
|
|
145
|
+
*/
|
|
146
|
+
export function resolveLegacyConfigSources(directory: string): {
|
|
147
|
+
user: LegacyConfigSource[];
|
|
148
|
+
project: LegacyConfigSource[];
|
|
149
|
+
} {
|
|
150
|
+
const userPaths = userScopeConfigPaths();
|
|
151
|
+
return {
|
|
152
|
+
user: [
|
|
153
|
+
...legacySourcesForBase(
|
|
154
|
+
join(configHome(), "opencode", CONFIG_FILE_BASENAME),
|
|
155
|
+
"OpenCode user",
|
|
156
|
+
),
|
|
157
|
+
...legacySourcesForBase(
|
|
158
|
+
join(homeDir(), ".pi", "agent", CONFIG_FILE_BASENAME),
|
|
159
|
+
"Pi user",
|
|
160
|
+
),
|
|
161
|
+
],
|
|
162
|
+
project: [
|
|
163
|
+
...legacySourcesForBase(join(directory, CONFIG_FILE_BASENAME), "project root"),
|
|
164
|
+
...legacySourcesForBase(
|
|
165
|
+
join(directory, ".opencode", CONFIG_FILE_BASENAME),
|
|
166
|
+
"OpenCode project",
|
|
167
|
+
),
|
|
168
|
+
...legacySourcesForBase(join(directory, ".pi", CONFIG_FILE_BASENAME), "Pi project"),
|
|
169
|
+
].filter((source) => !userPaths.has(source.path)),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type ConfigHarness = "opencode" | "pi";
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Legacy sources owned by ONE harness, most-specific first. Used by the loaders
|
|
177
|
+
* as a NON-DESTRUCTIVE read fallback: when the shared CortexKit base is absent
|
|
178
|
+
* (migration not yet run, or refused because OpenCode and Pi legacy configs
|
|
179
|
+
* differ), the running harness reads its OWN legacy config rather than silently
|
|
180
|
+
* falling back to schema defaults — which would re-enable features the legacy
|
|
181
|
+
* config disabled. Each harness reads only its own files, so a differing pair
|
|
182
|
+
* stays correct per-harness until the user consolidates. The bare project-root
|
|
183
|
+
* `<root>/magic-context.*` was OpenCode-only historically.
|
|
184
|
+
*/
|
|
185
|
+
export function resolveLegacyConfigSourcesForHarness(
|
|
186
|
+
directory: string,
|
|
187
|
+
harness: ConfigHarness,
|
|
188
|
+
): { user: LegacyConfigSource[]; project: LegacyConfigSource[] } {
|
|
189
|
+
if (harness === "pi") {
|
|
190
|
+
return {
|
|
191
|
+
user: legacySourcesForBase(
|
|
192
|
+
join(homeDir(), ".pi", "agent", CONFIG_FILE_BASENAME),
|
|
193
|
+
"Pi user",
|
|
194
|
+
),
|
|
195
|
+
project: legacySourcesForBase(
|
|
196
|
+
join(directory, ".pi", CONFIG_FILE_BASENAME),
|
|
197
|
+
"Pi project",
|
|
198
|
+
),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
user: legacySourcesForBase(
|
|
203
|
+
join(configHome(), "opencode", CONFIG_FILE_BASENAME),
|
|
204
|
+
"OpenCode user",
|
|
205
|
+
),
|
|
206
|
+
project: [
|
|
207
|
+
...legacySourcesForBase(join(directory, CONFIG_FILE_BASENAME), "project root"),
|
|
208
|
+
...legacySourcesForBase(
|
|
209
|
+
join(directory, ".opencode", CONFIG_FILE_BASENAME),
|
|
210
|
+
"OpenCode project",
|
|
211
|
+
),
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── JSONC semantics comparison (dependency-free, ported from AFT) ─────────────
|
|
217
|
+
// A legacy source that semantically MATCHES an existing target is moved aside
|
|
218
|
+
// (target wins); one that DIFFERS triggers refuse-and-warn (never auto-clobber).
|
|
219
|
+
// We strip comments/trailing-commas and sort keys so formatting/comment/order
|
|
220
|
+
// differences don't read as a conflict.
|
|
221
|
+
|
|
222
|
+
function stripJsoncForParse(input: string): string {
|
|
223
|
+
let out = "";
|
|
224
|
+
let inString = false;
|
|
225
|
+
let escaped = false;
|
|
226
|
+
for (let i = 0; i < input.length; i++) {
|
|
227
|
+
const ch = input[i];
|
|
228
|
+
const next = input[i + 1];
|
|
229
|
+
if (inString) {
|
|
230
|
+
out += ch;
|
|
231
|
+
if (escaped) escaped = false;
|
|
232
|
+
else if (ch === "\\") escaped = true;
|
|
233
|
+
else if (ch === '"') inString = false;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (ch === '"') {
|
|
237
|
+
inString = true;
|
|
238
|
+
out += ch;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (ch === "/" && next === "/") {
|
|
242
|
+
while (i < input.length && input[i] !== "\n") i++;
|
|
243
|
+
out += "\n";
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (ch === "/" && next === "*") {
|
|
247
|
+
i += 2;
|
|
248
|
+
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
|
|
249
|
+
i++;
|
|
250
|
+
out += " ";
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
out += ch;
|
|
254
|
+
}
|
|
255
|
+
let withoutTrailingCommas = "";
|
|
256
|
+
inString = false;
|
|
257
|
+
escaped = false;
|
|
258
|
+
for (let i = 0; i < out.length; i++) {
|
|
259
|
+
const ch = out[i];
|
|
260
|
+
if (inString) {
|
|
261
|
+
withoutTrailingCommas += ch;
|
|
262
|
+
if (escaped) escaped = false;
|
|
263
|
+
else if (ch === "\\") escaped = true;
|
|
264
|
+
else if (ch === '"') inString = false;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (ch === '"') {
|
|
268
|
+
inString = true;
|
|
269
|
+
withoutTrailingCommas += ch;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (ch === ",") {
|
|
273
|
+
let j = i + 1;
|
|
274
|
+
while (j < out.length && /\s/.test(out[j])) j++;
|
|
275
|
+
if (out[j] === "}" || out[j] === "]") continue;
|
|
276
|
+
}
|
|
277
|
+
withoutTrailingCommas += ch;
|
|
278
|
+
}
|
|
279
|
+
return withoutTrailingCommas;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function sortJson(value: unknown): unknown {
|
|
283
|
+
if (Array.isArray(value)) return value.map(sortJson);
|
|
284
|
+
if (value && typeof value === "object") {
|
|
285
|
+
const sorted: Record<string, unknown> = {};
|
|
286
|
+
for (const key of Object.keys(value as Record<string, unknown>).sort()) {
|
|
287
|
+
sorted[key] = sortJson((value as Record<string, unknown>)[key]);
|
|
288
|
+
}
|
|
289
|
+
return sorted;
|
|
290
|
+
}
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function normalizedJsoncSemantics(content: string): string {
|
|
295
|
+
return JSON.stringify(sortJson(JSON.parse(stripJsoncForParse(content))));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function fileSemanticsMatch(a: string, b: string): boolean {
|
|
299
|
+
try {
|
|
300
|
+
return normalizedJsoncSemantics(a) === normalizedJsoncSemantics(b);
|
|
301
|
+
} catch {
|
|
302
|
+
// If either side can't be parsed (truly malformed), fall back to an
|
|
303
|
+
// exact-bytes comparison — a parse-broken legacy file that differs byte
|
|
304
|
+
// for byte is conservatively treated as a conflict, never auto-clobbered.
|
|
305
|
+
return a === b;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── Cross-process lock (Desktop runs many instances concurrently) ────────────
|
|
310
|
+
|
|
311
|
+
// A real migration is a sub-second rename+write, so a lock older than this was
|
|
312
|
+
// almost certainly left by a crashed holder and is safe to reclaim.
|
|
313
|
+
const CONFIG_LOCK_STALE_MS = 4_000;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Directory-mkdir lock. `mkdir` is atomic; EEXIST means another instance holds
|
|
317
|
+
* it. We do NOT block plugin init: migration is not correctness-critical this
|
|
318
|
+
* run (the loaders read-legacy-on-conflict when the CortexKit base is absent),
|
|
319
|
+
* so on a LIVE competing holder we return null immediately and skip migration
|
|
320
|
+
* this run — the holder finishes its sub-second rename+write, and the next init
|
|
321
|
+
* sees target-present and consolidates. The ONLY retry is a single one-shot
|
|
322
|
+
* stale reclaim: a crashed holder's lock dir (older than the stale threshold) is
|
|
323
|
+
* removed and we re-attempt mkdir once. No busy-wait, no synchronous sleep — the
|
|
324
|
+
* init path never blocks on lock contention.
|
|
325
|
+
*/
|
|
326
|
+
function acquireConfigMigrationLock(lockDir: string): (() => void) | null {
|
|
327
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
328
|
+
try {
|
|
329
|
+
mkdirSync(lockDir, { recursive: false });
|
|
330
|
+
return () => {
|
|
331
|
+
try {
|
|
332
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
333
|
+
} catch {
|
|
334
|
+
// best-effort lock cleanup
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
} catch (err) {
|
|
338
|
+
const code = (err as { code?: unknown })?.code;
|
|
339
|
+
if (code !== "EEXIST") throw err;
|
|
340
|
+
// EEXIST: another instance holds the lock. Reclaim ONLY if it's stale
|
|
341
|
+
// (crashed holder) and retry mkdir once; otherwise a live holder owns
|
|
342
|
+
// it → skip this run rather than block init.
|
|
343
|
+
try {
|
|
344
|
+
const ageMs = Date.now() - statSync(lockDir).mtimeMs;
|
|
345
|
+
if (ageMs > CONFIG_LOCK_STALE_MS) {
|
|
346
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
// Lock vanished between stat and now — retry mkdir once.
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── Atomic writes ────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
function atomicCopyConfigFile(sourcePath: string, targetPath: string): void {
|
|
362
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
363
|
+
const tmpPath = join(
|
|
364
|
+
dirname(targetPath),
|
|
365
|
+
`.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,
|
|
366
|
+
);
|
|
367
|
+
let fd: number | null = null;
|
|
368
|
+
try {
|
|
369
|
+
fd = openSync(tmpPath, "wx", 0o600);
|
|
370
|
+
writeFileSync(fd, readFileSync(sourcePath));
|
|
371
|
+
closeSync(fd);
|
|
372
|
+
fd = null;
|
|
373
|
+
renameSync(tmpPath, targetPath);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
if (fd !== null) {
|
|
376
|
+
try {
|
|
377
|
+
closeSync(fd);
|
|
378
|
+
} catch {
|
|
379
|
+
// best-effort close before cleanup
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
unlinkSync(tmpPath);
|
|
384
|
+
} catch {
|
|
385
|
+
// best-effort temp cleanup
|
|
386
|
+
}
|
|
387
|
+
throw err;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function atomicWriteConfigFile(targetPath: string, content: string): void {
|
|
392
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
393
|
+
const tmpPath = join(
|
|
394
|
+
dirname(targetPath),
|
|
395
|
+
`.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,
|
|
396
|
+
);
|
|
397
|
+
let fd: number | null = null;
|
|
398
|
+
try {
|
|
399
|
+
fd = openSync(tmpPath, "wx", 0o600);
|
|
400
|
+
writeFileSync(fd, content);
|
|
401
|
+
closeSync(fd);
|
|
402
|
+
fd = null;
|
|
403
|
+
renameSync(tmpPath, targetPath);
|
|
404
|
+
} catch (err) {
|
|
405
|
+
if (fd !== null) {
|
|
406
|
+
try {
|
|
407
|
+
closeSync(fd);
|
|
408
|
+
} catch {
|
|
409
|
+
// best-effort close before cleanup
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
unlinkSync(tmpPath);
|
|
414
|
+
} catch {
|
|
415
|
+
// best-effort temp cleanup
|
|
416
|
+
}
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ── Marker ───────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
function movedMarkerContent(
|
|
424
|
+
targetPath: string,
|
|
425
|
+
originalName: string,
|
|
426
|
+
originalContent: string,
|
|
427
|
+
): string {
|
|
428
|
+
const header = [
|
|
429
|
+
"// Magic Context configuration moved.",
|
|
430
|
+
"//",
|
|
431
|
+
"// Magic Context now reads its configuration from one shared CortexKit",
|
|
432
|
+
"// location instead of a per-agent path. The settings that were in this",
|
|
433
|
+
"// file have been moved to:",
|
|
434
|
+
"//",
|
|
435
|
+
`// ${targetPath}`,
|
|
436
|
+
"//",
|
|
437
|
+
"// Edit that file to change Magic Context settings. This location is no",
|
|
438
|
+
"// longer read by Magic Context.",
|
|
439
|
+
"//",
|
|
440
|
+
`// To undo, rename this file back to "${originalName}" (and remove the`,
|
|
441
|
+
"// CortexKit copy above if you want this location to take precedence).",
|
|
442
|
+
"//",
|
|
443
|
+
"// Your original settings are preserved below for reference.",
|
|
444
|
+
"",
|
|
445
|
+
"",
|
|
446
|
+
].join("\n");
|
|
447
|
+
return `${header}${originalContent}`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* After the live config is safely at the CortexKit target, rename each legacy
|
|
452
|
+
* source aside to `<name>.MOVED_READPLEASE` so a user editing the old path
|
|
453
|
+
* notices it is no longer read (a copy-in-place leaves a silent stale-edit
|
|
454
|
+
* trap). A failure here is non-fatal: the content is already at the target, so
|
|
455
|
+
* we warn and leave the legacy file in place.
|
|
456
|
+
*/
|
|
457
|
+
function markLegacySourcesMovedAside(
|
|
458
|
+
sources: readonly { path: string }[],
|
|
459
|
+
targetPath: string,
|
|
460
|
+
logger?: ConfigMigrationLogger,
|
|
461
|
+
): string[] {
|
|
462
|
+
const warnings: string[] = [];
|
|
463
|
+
const info = logger?.info ?? logger?.log;
|
|
464
|
+
for (const source of sources) {
|
|
465
|
+
const markerPath = `${source.path}${MOVED_MARKER_SUFFIX}`;
|
|
466
|
+
try {
|
|
467
|
+
const original = readFileSync(source.path, "utf-8");
|
|
468
|
+
atomicWriteConfigFile(
|
|
469
|
+
markerPath,
|
|
470
|
+
movedMarkerContent(targetPath, basename(source.path), original),
|
|
471
|
+
);
|
|
472
|
+
unlinkSync(source.path);
|
|
473
|
+
info?.(
|
|
474
|
+
`Moved legacy Magic Context config ${source.path} aside to ${markerPath}; now reading ${targetPath}`,
|
|
475
|
+
);
|
|
476
|
+
} catch (err) {
|
|
477
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
478
|
+
warnings.push(
|
|
479
|
+
`Magic Context could not move legacy config ${source.path} aside (${msg}); it is now stale and ignored. Delete it manually — config is read from ${targetPath}.`,
|
|
480
|
+
);
|
|
481
|
+
logger?.warn?.(
|
|
482
|
+
`Could not move legacy Magic Context config ${source.path} aside (${msg}); reading ${targetPath}`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return warnings;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function visibleConfigMigrationWarning(
|
|
490
|
+
scope: "user" | "project",
|
|
491
|
+
targetPath: string,
|
|
492
|
+
paths: readonly string[],
|
|
493
|
+
reason: string,
|
|
494
|
+
): string {
|
|
495
|
+
const uniquePaths = [...new Set([targetPath, ...paths])];
|
|
496
|
+
return (
|
|
497
|
+
`Magic Context ${scope} config migration refused: ${reason}. ` +
|
|
498
|
+
`Legacy and CortexKit config paths collapse to one file, but Magic Context will not overwrite or merge them automatically. ` +
|
|
499
|
+
`Please consolidate manually into ${targetPath}. Paths: ${uniquePaths.join(" ; ")}`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ── Core per-file migration (ported from AFT migrateAftConfigFile) ───────────
|
|
504
|
+
|
|
505
|
+
export function migrateConfigFile(opts: ConfigFileMigrationOptions): ConfigFileMigrationResult {
|
|
506
|
+
const warnings: string[] = [];
|
|
507
|
+
const existingSources = opts.legacySources.filter((source) => existsSync(source.path));
|
|
508
|
+
const info = opts.logger?.info ?? opts.logger?.log;
|
|
509
|
+
|
|
510
|
+
if (existingSources.length === 0) {
|
|
511
|
+
return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
mkdirSync(dirname(opts.targetPath), { recursive: true });
|
|
515
|
+
const release = acquireConfigMigrationLock(`${opts.targetPath}.lock`);
|
|
516
|
+
if (!release) {
|
|
517
|
+
// Another instance holds the lock past our short budget. Skip rather than
|
|
518
|
+
// block init; the loaders read-legacy-on-conflict so config is correct
|
|
519
|
+
// this run, and the next init retries the migration.
|
|
520
|
+
warnings.push(
|
|
521
|
+
`Config migration for ${opts.scope} skipped this run (another instance is migrating); will retry on next start.`,
|
|
522
|
+
);
|
|
523
|
+
return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const sources = existingSources.map((source) => ({
|
|
527
|
+
...source,
|
|
528
|
+
content: readFileSync(source.path, "utf-8"),
|
|
529
|
+
}));
|
|
530
|
+
|
|
531
|
+
if (existsSync(opts.targetPath)) {
|
|
532
|
+
const targetContent = readFileSync(opts.targetPath, "utf-8");
|
|
533
|
+
const differing = sources.filter(
|
|
534
|
+
(source) => !fileSemanticsMatch(source.content, targetContent),
|
|
535
|
+
);
|
|
536
|
+
if (differing.length > 0) {
|
|
537
|
+
const message = visibleConfigMigrationWarning(
|
|
538
|
+
opts.scope,
|
|
539
|
+
opts.targetPath,
|
|
540
|
+
differing.map((source) => source.path),
|
|
541
|
+
"the CortexKit target already exists with different settings",
|
|
542
|
+
);
|
|
543
|
+
warnings.push(message);
|
|
544
|
+
opts.logger?.warn?.(message);
|
|
545
|
+
return { migrated: false, conflict: true, targetPath: opts.targetPath, warnings };
|
|
546
|
+
}
|
|
547
|
+
info?.(
|
|
548
|
+
`Magic Context ${opts.scope} config already present at ${opts.targetPath}; legacy copies match`,
|
|
549
|
+
);
|
|
550
|
+
warnings.push(...markLegacySourcesMovedAside(sources, opts.targetPath, opts.logger));
|
|
551
|
+
return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const first = sources[0];
|
|
555
|
+
const differing = sources.filter(
|
|
556
|
+
(source) => !fileSemanticsMatch(source.content, first.content),
|
|
557
|
+
);
|
|
558
|
+
if (differing.length > 0) {
|
|
559
|
+
const message = visibleConfigMigrationWarning(
|
|
560
|
+
opts.scope,
|
|
561
|
+
opts.targetPath,
|
|
562
|
+
sources.map((source) => source.path),
|
|
563
|
+
"multiple legacy sources have different settings",
|
|
564
|
+
);
|
|
565
|
+
warnings.push(message);
|
|
566
|
+
opts.logger?.warn?.(message);
|
|
567
|
+
return { migrated: false, conflict: true, targetPath: opts.targetPath, warnings };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
atomicCopyConfigFile(first.path, opts.targetPath);
|
|
571
|
+
info?.(
|
|
572
|
+
`Migrated Magic Context ${opts.scope} config from ${first.path} to ${opts.targetPath}`,
|
|
573
|
+
);
|
|
574
|
+
warnings.push(...markLegacySourcesMovedAside(sources, opts.targetPath, opts.logger));
|
|
575
|
+
return {
|
|
576
|
+
migrated: true,
|
|
577
|
+
conflict: false,
|
|
578
|
+
sourcePath: first.path,
|
|
579
|
+
targetPath: opts.targetPath,
|
|
580
|
+
warnings,
|
|
581
|
+
};
|
|
582
|
+
} catch (err) {
|
|
583
|
+
const message = visibleConfigMigrationWarning(
|
|
584
|
+
opts.scope,
|
|
585
|
+
opts.targetPath,
|
|
586
|
+
existingSources.map((source) => source.path),
|
|
587
|
+
`migration failed (${err instanceof Error ? err.message : String(err)})`,
|
|
588
|
+
);
|
|
589
|
+
warnings.push(message);
|
|
590
|
+
opts.logger?.warn?.(message);
|
|
591
|
+
return { migrated: false, conflict: true, targetPath: opts.targetPath, warnings };
|
|
592
|
+
} finally {
|
|
593
|
+
release();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Run both the user-scope and project-scope config-location migrations for a
|
|
599
|
+
* project directory. Idempotent and cheap when nothing to migrate (a few
|
|
600
|
+
* existsSync calls). Call once at plugin init, before loading config. Returns
|
|
601
|
+
* any warnings (conflicts / partial failures) for the host to surface + log.
|
|
602
|
+
*/
|
|
603
|
+
export function migrateMagicContextConfigLocations(
|
|
604
|
+
directory: string,
|
|
605
|
+
logger?: ConfigMigrationLogger,
|
|
606
|
+
): string[] {
|
|
607
|
+
const warnings: string[] = [];
|
|
608
|
+
const legacy = resolveLegacyConfigSources(directory);
|
|
609
|
+
try {
|
|
610
|
+
warnings.push(
|
|
611
|
+
...migrateConfigFile({
|
|
612
|
+
scope: "user",
|
|
613
|
+
targetPath: resolveCortexKitUserConfigPath(),
|
|
614
|
+
legacySources: legacy.user,
|
|
615
|
+
logger,
|
|
616
|
+
}).warnings,
|
|
617
|
+
);
|
|
618
|
+
warnings.push(
|
|
619
|
+
...migrateConfigFile({
|
|
620
|
+
scope: "project",
|
|
621
|
+
targetPath: resolveCortexKitProjectConfigPath(directory),
|
|
622
|
+
legacySources: legacy.project,
|
|
623
|
+
logger,
|
|
624
|
+
}).warnings,
|
|
625
|
+
);
|
|
626
|
+
} catch (err) {
|
|
627
|
+
// Fail OPEN: a migration failure must never abort plugin init. Worst
|
|
628
|
+
// case the loader reads the CortexKit path (possibly empty) and a legacy
|
|
629
|
+
// file is left in place for the user to consolidate.
|
|
630
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
631
|
+
logger?.warn?.(`Magic Context config-location migration error (continuing): ${msg}`);
|
|
632
|
+
warnings.push(`Magic Context config-location migration error: ${msg}`);
|
|
633
|
+
}
|
|
634
|
+
return warnings;
|
|
635
|
+
}
|