@cortexkit/opencode-magic-context 0.5.1 → 0.6.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/README.md +18 -2
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli.js +23 -6
- package/dist/config/schema/magic-context.d.ts +21 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/compaction-marker.d.ts +72 -0
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/runner.d.ts +8 -0
- package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +8 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -0
- package/dist/features/magic-context/plugin-messages.d.ts +75 -0
- package/dist/features/magic-context/plugin-messages.d.ts.map +1 -0
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +10 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-notes.d.ts +51 -5
- package/dist/features/magic-context/storage-notes.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -2
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts +20 -0
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -0
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts +33 -0
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -0
- package/dist/hooks/magic-context/command-handler.d.ts +4 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts +27 -0
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts +1 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts +4 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-compressor.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-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +5 -0
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +7 -0
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +2 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +4 -0
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +2 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1231 -151
- package/dist/plugin/dream-timer.d.ts +4 -0
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/tui-action-consumer.d.ts +13 -0
- package/dist/plugin/tui-action-consumer.d.ts.map +1 -0
- package/dist/shared/tui-config.d.ts.map +1 -1
- package/dist/tools/ctx-note/constants.d.ts +1 -1
- package/dist/tools/ctx-note/constants.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tools/ctx-note/types.d.ts +3 -1
- package/dist/tools/ctx-note/types.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +20 -0
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/shared/assistant-message-extractor.ts +74 -0
- package/src/shared/conflict-detector.ts +310 -0
- package/src/shared/conflict-fixer.ts +216 -0
- package/src/shared/data-path.ts +10 -0
- package/src/shared/error-message.ts +3 -0
- package/src/shared/format-bytes.ts +5 -0
- package/src/shared/index.ts +4 -0
- package/src/shared/internal-initiator-marker.ts +1 -0
- package/src/shared/jsonc-parser.ts +138 -0
- package/src/shared/logger.ts +63 -0
- package/src/shared/model-requirements.ts +84 -0
- package/src/shared/model-suggestion-retry.ts +160 -0
- package/src/shared/normalize-sdk-response.ts +40 -0
- package/src/shared/opencode-compaction-detector.test.ts +222 -0
- package/src/shared/opencode-compaction-detector.ts +81 -0
- package/src/shared/opencode-config-dir-types.ts +15 -0
- package/src/shared/opencode-config-dir.ts +38 -0
- package/src/shared/record-type-guard.ts +3 -0
- package/src/shared/system-directive.ts +9 -0
- package/src/shared/tui-config.ts +60 -0
- package/src/tui/data/context-db.ts +114 -6
- package/src/tui/index.tsx +77 -2
- package/src/tui/slots/sidebar-content.tsx +3 -2
- package/dist/features/magic-context/storage-smart-notes.d.ts +0 -24
- package/dist/features/magic-context/storage-smart-notes.d.ts.map +0 -1
|
@@ -13,5 +13,9 @@ export declare function startDreamScheduleTimer(args: {
|
|
|
13
13
|
dreamerConfig?: DreamerConfig;
|
|
14
14
|
embeddingConfig: EmbeddingConfig;
|
|
15
15
|
memoryEnabled: boolean;
|
|
16
|
+
experimentalUserMemories?: {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
promotionThreshold: number;
|
|
19
|
+
};
|
|
16
20
|
}): (() => void) | undefined;
|
|
17
21
|
//# sourceMappingURL=dream-timer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dream-timer.d.ts","sourceRoot":"","sources":["../../src/plugin/dream-timer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAMrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"dream-timer.d.ts","sourceRoot":"","sources":["../../src/plugin/dream-timer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAMrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/E,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAqE3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAU7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;CAC1C;;;;;;
|
|
1
|
+
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAU7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;CAC1C;;;;;;qBA4C+R,CAAC;;;;;;;;;;;;qBATjR,CAAN;mBAAiB,CAAC;;;;;0BASwtW,CAAC;;;;;;EADpvW"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MagicContextConfig } from "../config/schema/magic-context";
|
|
2
|
+
import type { PluginContext } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Start a server-side consumer that polls plugin_messages for TUI→server
|
|
5
|
+
* action messages and dispatches them. Currently handles:
|
|
6
|
+
* - { command: "recomp" } — executes /ctx-recomp for the given session
|
|
7
|
+
*/
|
|
8
|
+
export declare function startTuiActionConsumer(args: {
|
|
9
|
+
client: PluginContext["client"];
|
|
10
|
+
directory: string;
|
|
11
|
+
config: MagicContextConfig;
|
|
12
|
+
}): (() => void) | undefined;
|
|
13
|
+
//# sourceMappingURL=tui-action-consumer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-action-consumer.d.ts","sourceRoot":"","sources":["../../src/plugin/tui-action-consumer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAMzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAQ7C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IACzC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;CAC9B,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CA+D3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CA+B9C"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CTX_NOTE_DESCRIPTION = "Save or inspect durable session notes that persist for this session.\nUse this for short goals, constraints, decisions, or reminders worth carrying forward.\n\nActions:\n- `write`: Append one note. Optionally provide `surface_condition` to create a smart note.\n- `read`: Show current notes
|
|
1
|
+
export declare const CTX_NOTE_DESCRIPTION = "Save or inspect durable session notes that persist for this session.\nUse this for short goals, constraints, decisions, or reminders worth carrying forward.\n\nActions:\n- `write`: Append one note. Optionally provide `surface_condition` to create a smart note.\n- `read`: Show current notes. Defaults to active session notes + ready smart notes; use `filter` to inspect all, pending, ready, active, or dismissed notes.\n- `dismiss`: Dismiss a note by `note_id`.\n- `update`: Update a note by `note_id`.\n\n**Smart Notes**: When `surface_condition` is provided with `write`, the note becomes a project-scoped smart note.\nThe dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.\nExample: `ctx_note(action=\"write\", content=\"Implement X because Y\", surface_condition=\"When PR #42 is merged in this repo\")`\n\nHistorian reads these notes, deduplicates them, and rewrites the remaining useful notes over time.";
|
|
2
2
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,s8BAakE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAchE,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAchE,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAkND,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAIxF"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export type CtxNoteReadFilter = "all" | "active" | "pending" | "ready" | "dismissed";
|
|
1
2
|
export interface CtxNoteArgs {
|
|
2
|
-
action?: "write" | "read" | "
|
|
3
|
+
action?: "write" | "read" | "dismiss" | "update";
|
|
3
4
|
content?: string;
|
|
4
5
|
surface_condition?: string;
|
|
6
|
+
filter?: CtxNoteReadFilter;
|
|
5
7
|
note_id?: number;
|
|
6
8
|
}
|
|
7
9
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IACxB,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAAC;AAErF,MAAM,WAAW,WAAW;IACxB,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -51,4 +51,24 @@ export interface StatusDetail extends SidebarSnapshot {
|
|
|
51
51
|
export declare function closeDb(): void;
|
|
52
52
|
export declare function loadSidebarSnapshot(sessionId: string, directory: string): SidebarSnapshot;
|
|
53
53
|
export declare function loadStatusDetail(sessionId: string, directory: string, modelKey?: string): StatusDetail;
|
|
54
|
+
/**
|
|
55
|
+
* Get compartment count for a session (used by recomp confirmation dialog).
|
|
56
|
+
*/
|
|
57
|
+
export declare function getCompartmentCount(sessionId: string): number;
|
|
58
|
+
/**
|
|
59
|
+
* Consume pending server→TUI messages from the plugin_messages table.
|
|
60
|
+
* Returns consumed messages and marks them as consumed.
|
|
61
|
+
*/
|
|
62
|
+
export interface TuiMessage {
|
|
63
|
+
id: number;
|
|
64
|
+
type: string;
|
|
65
|
+
payload: Record<string, unknown>;
|
|
66
|
+
sessionId: string | null;
|
|
67
|
+
createdAt: number;
|
|
68
|
+
}
|
|
69
|
+
export declare function consumeTuiMessages(): TuiMessage[];
|
|
70
|
+
/**
|
|
71
|
+
* Send a message from TUI to server via plugin_messages.
|
|
72
|
+
*/
|
|
73
|
+
export declare function sendMessageToServer(type: string, payload: Record<string, unknown>, sessionId?: string): boolean;
|
|
54
74
|
//# sourceMappingURL=context-db.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAExD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IAEtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IAEvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AA+BD,wBAAgB,OAAO,IAAI,IAAI,CAU9B;AA+BD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAuNzF;AAED,wBAAgB,gBAAgB,CAC5B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,YAAY,CAmMd"}
|
|
1
|
+
{"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAExD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IAEtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IAEvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AA+BD,wBAAgB,OAAO,IAAI,IAAI,CAU9B;AA+BD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAuNzF;AAED,wBAAgB,gBAAgB,CAC5B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,YAAY,CAmMd;AAyCD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAW7D;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,kBAAkB,IAAI,UAAU,EAAE,CAoDjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAiBT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/opencode-magic-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Magic Context — cross-session memory and context management",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
30
|
"src/tui",
|
|
31
|
+
"src/shared",
|
|
31
32
|
"README.md"
|
|
32
33
|
],
|
|
33
34
|
"scripts": {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
type MessageTime = { created?: number };
|
|
2
|
+
|
|
3
|
+
type MessageInfo = {
|
|
4
|
+
role?: string;
|
|
5
|
+
time?: MessageTime;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type MessagePart = {
|
|
9
|
+
type?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type SessionMessage = {
|
|
14
|
+
info?: MessageInfo;
|
|
15
|
+
parts?: unknown;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
import { isRecord } from "./record-type-guard";
|
|
19
|
+
|
|
20
|
+
function asSessionMessage(value: unknown): SessionMessage | null {
|
|
21
|
+
if (!isRecord(value)) return null;
|
|
22
|
+
const info = value.info;
|
|
23
|
+
const parts = value.parts;
|
|
24
|
+
return {
|
|
25
|
+
info: isRecord(info)
|
|
26
|
+
? {
|
|
27
|
+
role: typeof info.role === "string" ? info.role : undefined,
|
|
28
|
+
time: isRecord(info.time)
|
|
29
|
+
? {
|
|
30
|
+
created:
|
|
31
|
+
typeof info.time.created === "number"
|
|
32
|
+
? info.time.created
|
|
33
|
+
: undefined,
|
|
34
|
+
}
|
|
35
|
+
: undefined,
|
|
36
|
+
}
|
|
37
|
+
: undefined,
|
|
38
|
+
parts,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getCreatedTime(message: SessionMessage): number {
|
|
43
|
+
return message.info?.time?.created ?? 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getTextParts(message: SessionMessage): MessagePart[] {
|
|
47
|
+
if (!Array.isArray(message.parts)) return [];
|
|
48
|
+
return message.parts
|
|
49
|
+
.filter((part): part is Record<string, unknown> => isRecord(part))
|
|
50
|
+
.map((part) => ({
|
|
51
|
+
type: typeof part.type === "string" ? part.type : undefined,
|
|
52
|
+
text: typeof part.text === "string" ? part.text : undefined,
|
|
53
|
+
}))
|
|
54
|
+
.filter((part) => part.type === "text" && Boolean(part.text));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function extractLatestAssistantText(messages: unknown): string | null {
|
|
58
|
+
if (!Array.isArray(messages) || messages.length === 0) return null;
|
|
59
|
+
|
|
60
|
+
const assistantMessages = messages
|
|
61
|
+
.map(asSessionMessage)
|
|
62
|
+
.filter((message): message is SessionMessage => message !== null)
|
|
63
|
+
.filter((message) => message.info?.role === "assistant")
|
|
64
|
+
.sort((a, b) => getCreatedTime(b) - getCreatedTime(a));
|
|
65
|
+
|
|
66
|
+
const latest = assistantMessages[0];
|
|
67
|
+
if (!latest) return null;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
getTextParts(latest)
|
|
71
|
+
.map((part) => part.text)
|
|
72
|
+
.join("\n") || null
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readJsoncFile } from "./jsonc-parser";
|
|
3
|
+
import { getOpenCodeConfigPaths } from "./opencode-config-dir";
|
|
4
|
+
|
|
5
|
+
interface OpenCodeConfig {
|
|
6
|
+
compaction?: {
|
|
7
|
+
auto?: boolean;
|
|
8
|
+
prune?: boolean;
|
|
9
|
+
};
|
|
10
|
+
plugin?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface OmoConfig {
|
|
14
|
+
disabled_hooks?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ConflictResult {
|
|
18
|
+
/** Whether any blocking conflict was found */
|
|
19
|
+
hasConflict: boolean;
|
|
20
|
+
/** Human-readable reasons for each conflict */
|
|
21
|
+
reasons: string[];
|
|
22
|
+
/** Which conflicts were found — used for targeted fixes */
|
|
23
|
+
conflicts: {
|
|
24
|
+
compactionAuto: boolean;
|
|
25
|
+
compactionPrune: boolean;
|
|
26
|
+
dcpPlugin: boolean;
|
|
27
|
+
omoPreemptiveCompaction: boolean;
|
|
28
|
+
omoContextWindowMonitor: boolean;
|
|
29
|
+
omoAnthropicRecovery: boolean;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect all conflicts that would prevent magic-context from working correctly.
|
|
35
|
+
* Checks: OpenCode compaction, DCP plugin, OMO conflicting hooks.
|
|
36
|
+
*/
|
|
37
|
+
export function detectConflicts(directory: string): ConflictResult {
|
|
38
|
+
const conflicts: ConflictResult["conflicts"] = {
|
|
39
|
+
compactionAuto: false,
|
|
40
|
+
compactionPrune: false,
|
|
41
|
+
dcpPlugin: false,
|
|
42
|
+
omoPreemptiveCompaction: false,
|
|
43
|
+
omoContextWindowMonitor: false,
|
|
44
|
+
omoAnthropicRecovery: false,
|
|
45
|
+
};
|
|
46
|
+
const reasons: string[] = [];
|
|
47
|
+
|
|
48
|
+
// --- Check OpenCode compaction config ---
|
|
49
|
+
const compactionResult = checkCompaction(directory);
|
|
50
|
+
if (compactionResult.auto) {
|
|
51
|
+
conflicts.compactionAuto = true;
|
|
52
|
+
reasons.push("OpenCode auto-compaction is enabled (compaction.auto=true)");
|
|
53
|
+
}
|
|
54
|
+
if (compactionResult.prune) {
|
|
55
|
+
conflicts.compactionPrune = true;
|
|
56
|
+
reasons.push("OpenCode prune is enabled (compaction.prune=true)");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// --- Check for DCP plugin ---
|
|
60
|
+
const dcpFound = checkDcpPlugin(directory);
|
|
61
|
+
if (dcpFound) {
|
|
62
|
+
conflicts.dcpPlugin = true;
|
|
63
|
+
reasons.push(
|
|
64
|
+
"opencode-dcp plugin is installed — it conflicts with Magic Context's context management",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Check OMO conflicting hooks ---
|
|
69
|
+
const omoResult = checkOmoHooks(directory);
|
|
70
|
+
if (omoResult.preemptiveCompaction) {
|
|
71
|
+
conflicts.omoPreemptiveCompaction = true;
|
|
72
|
+
reasons.push(
|
|
73
|
+
"oh-my-opencode preemptive-compaction hook is active — it triggers compaction that conflicts with historian",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (omoResult.contextWindowMonitor) {
|
|
77
|
+
conflicts.omoContextWindowMonitor = true;
|
|
78
|
+
reasons.push(
|
|
79
|
+
"oh-my-opencode context-window-monitor hook is active — it injects usage warnings that overlap with Magic Context nudges",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (omoResult.anthropicRecovery) {
|
|
83
|
+
conflicts.omoAnthropicRecovery = true;
|
|
84
|
+
reasons.push(
|
|
85
|
+
"oh-my-opencode anthropic-context-window-limit-recovery hook is active — it triggers emergency compaction that bypasses historian",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
hasConflict: reasons.length > 0,
|
|
91
|
+
reasons,
|
|
92
|
+
conflicts,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Compaction detection (extracted from opencode-compaction-detector.ts) ---
|
|
97
|
+
|
|
98
|
+
function checkCompaction(directory: string): { auto: boolean; prune: boolean } {
|
|
99
|
+
if (process.env.OPENCODE_DISABLE_AUTOCOMPACT) {
|
|
100
|
+
return { auto: false, prune: false };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check project-level config first (higher precedence)
|
|
104
|
+
const projectResult = readProjectCompaction(directory);
|
|
105
|
+
if (projectResult.resolved) return projectResult;
|
|
106
|
+
|
|
107
|
+
// Fall back to user-level config
|
|
108
|
+
const userResult = readUserCompaction();
|
|
109
|
+
if (userResult.resolved) return userResult;
|
|
110
|
+
|
|
111
|
+
// Default: OpenCode has compaction enabled by default
|
|
112
|
+
return { auto: true, prune: false };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readProjectCompaction(directory: string): {
|
|
116
|
+
auto: boolean;
|
|
117
|
+
prune: boolean;
|
|
118
|
+
resolved: boolean;
|
|
119
|
+
} {
|
|
120
|
+
// .opencode/ config has higher precedence
|
|
121
|
+
const dotOcJsonc = join(directory, ".opencode", "opencode.jsonc");
|
|
122
|
+
const dotOcJson = join(directory, ".opencode", "opencode.json");
|
|
123
|
+
const dotOcConfig =
|
|
124
|
+
readJsoncFile<OpenCodeConfig>(dotOcJsonc) ?? readJsoncFile<OpenCodeConfig>(dotOcJson);
|
|
125
|
+
|
|
126
|
+
if (dotOcConfig?.compaction) {
|
|
127
|
+
const c = dotOcConfig.compaction;
|
|
128
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
129
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Root-level project config
|
|
134
|
+
const rootJsonc = join(directory, "opencode.jsonc");
|
|
135
|
+
const rootJson = join(directory, "opencode.json");
|
|
136
|
+
const rootConfig =
|
|
137
|
+
readJsoncFile<OpenCodeConfig>(rootJsonc) ?? readJsoncFile<OpenCodeConfig>(rootJson);
|
|
138
|
+
|
|
139
|
+
if (rootConfig?.compaction) {
|
|
140
|
+
const c = rootConfig.compaction;
|
|
141
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
142
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { auto: false, prune: false, resolved: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function readUserCompaction(): { auto: boolean; prune: boolean; resolved: boolean } {
|
|
150
|
+
try {
|
|
151
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
152
|
+
const config =
|
|
153
|
+
readJsoncFile<OpenCodeConfig>(paths.configJsonc) ??
|
|
154
|
+
readJsoncFile<OpenCodeConfig>(paths.configJson);
|
|
155
|
+
|
|
156
|
+
if (config?.compaction) {
|
|
157
|
+
const c = config.compaction;
|
|
158
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
159
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Intentional: config read is best-effort
|
|
164
|
+
}
|
|
165
|
+
return { auto: false, prune: false, resolved: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- DCP detection ---
|
|
169
|
+
|
|
170
|
+
function checkDcpPlugin(directory: string): boolean {
|
|
171
|
+
const plugins = collectPluginEntries(directory);
|
|
172
|
+
return plugins.some((p) => p.includes("opencode-dcp"));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function collectPluginEntries(directory: string): string[] {
|
|
176
|
+
const plugins: string[] = [];
|
|
177
|
+
|
|
178
|
+
// Project-level configs
|
|
179
|
+
for (const configPath of [
|
|
180
|
+
join(directory, ".opencode", "opencode.jsonc"),
|
|
181
|
+
join(directory, ".opencode", "opencode.json"),
|
|
182
|
+
join(directory, "opencode.jsonc"),
|
|
183
|
+
join(directory, "opencode.json"),
|
|
184
|
+
]) {
|
|
185
|
+
const config = readJsoncFile<OpenCodeConfig>(configPath);
|
|
186
|
+
if (config?.plugin) {
|
|
187
|
+
plugins.push(...config.plugin);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// User-level config
|
|
192
|
+
try {
|
|
193
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
194
|
+
for (const configPath of [paths.configJsonc, paths.configJson]) {
|
|
195
|
+
const config = readJsoncFile<OpenCodeConfig>(configPath);
|
|
196
|
+
if (config?.plugin) {
|
|
197
|
+
plugins.push(...config.plugin);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// best-effort
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return plugins;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- OMO hook detection ---
|
|
208
|
+
|
|
209
|
+
function checkOmoHooks(directory: string): {
|
|
210
|
+
preemptiveCompaction: boolean;
|
|
211
|
+
contextWindowMonitor: boolean;
|
|
212
|
+
anthropicRecovery: boolean;
|
|
213
|
+
} {
|
|
214
|
+
const result = {
|
|
215
|
+
preemptiveCompaction: false,
|
|
216
|
+
contextWindowMonitor: false,
|
|
217
|
+
anthropicRecovery: false,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// First check if OMO is even installed
|
|
221
|
+
const plugins = collectPluginEntries(directory);
|
|
222
|
+
const hasOmo = plugins.some(
|
|
223
|
+
(p) =>
|
|
224
|
+
p.includes("oh-my-opencode") ||
|
|
225
|
+
p.includes("oh-my-openagent") ||
|
|
226
|
+
p.includes("@code-yeongyu/"),
|
|
227
|
+
);
|
|
228
|
+
if (!hasOmo) return result;
|
|
229
|
+
|
|
230
|
+
// Read OMO config to check disabled_hooks
|
|
231
|
+
const disabledHooks = readOmoDisabledHooks(directory);
|
|
232
|
+
|
|
233
|
+
// Hooks are ACTIVE unless explicitly in disabled_hooks
|
|
234
|
+
result.preemptiveCompaction = !disabledHooks.has("preemptive-compaction");
|
|
235
|
+
result.contextWindowMonitor = !disabledHooks.has("context-window-monitor");
|
|
236
|
+
result.anthropicRecovery = !disabledHooks.has("anthropic-context-window-limit-recovery");
|
|
237
|
+
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function readOmoDisabledHooks(directory: string): Set<string> {
|
|
242
|
+
const disabled = new Set<string>();
|
|
243
|
+
|
|
244
|
+
// Check both old and new OMO config names
|
|
245
|
+
const configNames = [
|
|
246
|
+
"oh-my-opencode.jsonc",
|
|
247
|
+
"oh-my-opencode.json",
|
|
248
|
+
"oh-my-openagent.jsonc",
|
|
249
|
+
"oh-my-openagent.json",
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
254
|
+
for (const name of configNames) {
|
|
255
|
+
const configPath = join(paths.configDir, name);
|
|
256
|
+
const config = readJsoncFile<OmoConfig>(configPath);
|
|
257
|
+
if (config?.disabled_hooks) {
|
|
258
|
+
for (const hook of config.disabled_hooks) {
|
|
259
|
+
disabled.add(hook);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
// best-effort
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Also check project-level OMO configs
|
|
268
|
+
for (const name of configNames) {
|
|
269
|
+
const config = readJsoncFile<OmoConfig>(join(directory, name));
|
|
270
|
+
if (config?.disabled_hooks) {
|
|
271
|
+
for (const hook of config.disabled_hooks) {
|
|
272
|
+
disabled.add(hook);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return disabled;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Generate a user-facing summary of conflicts for display in dialogs/notifications.
|
|
282
|
+
*/
|
|
283
|
+
export function formatConflictSummary(result: ConflictResult): string {
|
|
284
|
+
if (!result.hasConflict) return "";
|
|
285
|
+
|
|
286
|
+
const lines = [
|
|
287
|
+
"⚠️ Magic Context is disabled due to conflicting configuration:\n",
|
|
288
|
+
...result.reasons.map((r) => ` • ${r}`),
|
|
289
|
+
"",
|
|
290
|
+
"Run `bunx @cortexkit/opencode-magic-context doctor` to fix,",
|
|
291
|
+
"or resolve these conflicts manually in your OpenCode config.",
|
|
292
|
+
];
|
|
293
|
+
return lines.join("\n");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate a short conflict summary for ignored message display.
|
|
298
|
+
*/
|
|
299
|
+
export function formatConflictShort(result: ConflictResult): string {
|
|
300
|
+
if (!result.hasConflict) return "";
|
|
301
|
+
|
|
302
|
+
const lines = [
|
|
303
|
+
"⚠️ Magic Context is disabled due to conflicting configuration:",
|
|
304
|
+
"",
|
|
305
|
+
...result.reasons.map((r) => `• ${r}`),
|
|
306
|
+
"",
|
|
307
|
+
"Fix: run `bunx @cortexkit/opencode-magic-context doctor`",
|
|
308
|
+
];
|
|
309
|
+
return lines.join("\n");
|
|
310
|
+
}
|