@cortexkit/opencode-magic-context 0.42.0 → 0.42.1

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.
Files changed (70) hide show
  1. package/dist/config/project-security.d.ts +3 -2
  2. package/dist/config/project-security.d.ts.map +1 -1
  3. package/dist/config/schema/magic-context.d.ts +20 -0
  4. package/dist/config/schema/magic-context.d.ts.map +1 -1
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +8 -4
  6. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  7. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  8. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
  9. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  10. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
  11. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-model-match.d.ts +41 -0
  13. package/dist/features/magic-context/memory/embedding-model-match.d.ts.map +1 -0
  14. package/dist/features/magic-context/memory/embedding-openai.d.ts +7 -20
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -2
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  18. package/dist/features/magic-context/memory/embedding-synapse.d.ts +5 -4
  19. package/dist/features/magic-context/memory/embedding-synapse.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  21. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  22. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  23. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/compartment-runner-validation.d.ts +1 -1
  26. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/embed-session-state.d.ts +1 -1
  28. package/dist/hooks/magic-context/embed-session-state.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/execute-status.d.ts +2 -0
  30. package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/format-embed-failure.d.ts +2 -1
  32. package/dist/hooks/magic-context/format-embed-failure.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/maintenance-authority.d.ts +2 -2
  34. package/dist/hooks/magic-context/maintenance-authority.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  36. package/dist/{index-c9hamh36.js → index-1q9kay7n.js} +534 -120
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +264 -108
  39. package/dist/plugin/embedding-bootstrap-helpers.d.ts.map +1 -1
  40. package/dist/plugin/embedding-routing.d.ts.map +1 -1
  41. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  42. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  43. package/dist/{recomp-orchestrator-3atsasjy.js → recomp-orchestrator-m33b3rk9.js} +1 -1
  44. package/dist/shared/rpc-types.d.ts +10 -0
  45. package/dist/shared/rpc-types.d.ts.map +1 -1
  46. package/dist/shared/status-detail-text.d.ts +3 -1
  47. package/dist/shared/status-detail-text.d.ts.map +1 -1
  48. package/dist/shared/status-summary.d.ts +30 -0
  49. package/dist/shared/status-summary.d.ts.map +1 -0
  50. package/dist/shared/tail-hygiene-status.d.ts +1 -0
  51. package/dist/shared/tail-hygiene-status.d.ts.map +1 -1
  52. package/dist/shared/user-facing-codes.d.ts +191 -0
  53. package/dist/shared/user-facing-codes.d.ts.map +1 -0
  54. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  55. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  56. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/src/config/project-security.test.ts +10 -2
  59. package/src/config/project-security.ts +12 -5
  60. package/src/config/schema/magic-context.test.ts +29 -0
  61. package/src/config/schema/magic-context.ts +24 -0
  62. package/src/shared/rpc-types.ts +10 -0
  63. package/src/shared/status-detail-text.test.ts +93 -24
  64. package/src/shared/status-detail-text.ts +9 -2
  65. package/src/shared/status-summary.ts +167 -0
  66. package/src/shared/tail-hygiene-status.ts +8 -0
  67. package/src/shared/user-facing-codes.test.ts +140 -0
  68. package/src/shared/user-facing-codes.ts +276 -0
  69. package/src/tui/index.tsx +41 -8
  70. package/src/tui-compiled/index.tsx +660 -647
@@ -1,7 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { formatOpenCodeDbMissingStatusLine, resolveOpenCodeDbPath } from "./opencode-db-path";
3
2
  import type { StatusDetail } from "./rpc-types";
4
- import { formatStatusDetailMarkdown } from "./status-detail-text";
3
+ import { formatStatusDetailMarkdown, formatStatusDiagnosticsMarkdown } from "./status-detail-text";
5
4
 
6
5
  const STATUS_FIXTURE: StatusDetail = {
7
6
  sessionId: "ses_status",
@@ -18,9 +17,11 @@ const STATUS_FIXTURE: StatusDetail = {
18
17
  compartmentInProgress: true,
19
18
  sessionNoteCount: 1,
20
19
  readySmartNoteCount: 2,
21
- cacheTtl: "5m",
22
- cacheTtlSource: "session",
20
+ cacheTtl: "1h",
21
+ cacheTtlSource: "config",
22
+ cacheTtlModelKey: "anthropic/claude-opus-5",
23
23
  lastTransformError: null,
24
+ historianFailureCount: 0,
24
25
  lastDreamerRunAt: null,
25
26
  projectIdentity: "/repo",
26
27
  compartmentTokens: 22_000,
@@ -62,38 +63,106 @@ const STATUS_FIXTURE: StatusDetail = {
62
63
  lastErrorMessage: null,
63
64
  lastErrorTime: null,
64
65
  },
66
+ tailHygiene: {
67
+ u: 14_400,
68
+ t: 48_000,
69
+ severity: 0.3,
70
+ evaluable: true,
71
+ generationInvalidated: false,
72
+ baselineGeneration: 3,
73
+ computedAt: 1_730_000_000_000,
74
+ reclaimableToolOutputCount: 3,
75
+ },
76
+ embedding: {
77
+ state: "running",
78
+ indexed: 9,
79
+ total: 12,
80
+ },
65
81
  storage_versions: {
66
82
  context_db_schema_version: 79,
67
83
  plugin_supported_version: 79,
68
84
  },
69
85
  };
70
86
 
71
- describe("formatStatusDetailMarkdown", () => {
72
- test("renders a missing-store condition before the fixed TUI status payload", () => {
73
- expect(formatStatusDetailMarkdown(STATUS_FIXTURE)).toBe(
74
- `${formatOpenCodeDbMissingStatusLine(resolveOpenCodeDbPath())}\n\n## Magic Context Status
87
+ describe("status detail text", () => {
88
+ test("renders the OpenCode summary golden", () => {
89
+ expect(formatStatusDetailMarkdown(STATUS_FIXTURE)).toBe(`## Magic Context Status
75
90
 
76
- - **Mode:** Magic Context compaction
77
- - **Active profile:** work
78
- - **Usage:** 75.0% (96,000 / 128,000 usable tokens)
79
- - **Cache TTL: 5m (session)**; live (42s remaining); TTL 5m
80
- - **Historian:** running; boundary present; coverage 12
81
- - **Memory:** 8 active; 3 injected
82
- - **Tags:** 4 active, 1 dropped; 2 pending drops
83
- - **Execute threshold:** 65.0%`,
84
- );
91
+ - **Context:** 75.0% of usable context (96,000 / 128,000 tokens)
92
+ - **Cache lifetime:** 1h (config for anthropic/claude-opus-5)
93
+ - **Automatic compression:** at 65.0% of usable context
94
+ - **History compression:** Compressing history · 12 history blocks
95
+ - **Reclaimable:** 3 spent tool outputs (~14k tokens)
96
+ - **Memory:** 8 memories · 3 notes
97
+ - **Search indexing:** Running · 9 / 12 history blocks indexed`);
85
98
  });
86
99
 
87
- test("shows module-routed host paths only in Rust-mode chat fallback", () => {
88
- const rustStatus = formatStatusDetailMarkdown({
100
+ test("renders disabled automatic compression and an empty reclaimable set without a gauge", () => {
101
+ const rendered = formatStatusDetailMarkdown({
102
+ ...STATUS_FIXTURE,
103
+ compaction_enabled: false,
104
+ tailHygiene: {
105
+ ...STATUS_FIXTURE.tailHygiene!,
106
+ u: 0,
107
+ reclaimableToolOutputCount: 0,
108
+ },
109
+ });
110
+ expect(rendered).toContain("- **Automatic compression:** Off");
111
+ expect(rendered).toContain("- **Reclaimable:** none");
112
+ expect(rendered).not.toContain("Reclaimable: 0");
113
+ });
114
+
115
+ test("renders summary and diagnostics from one snapshot with matching status values", () => {
116
+ const summary = formatStatusDetailMarkdown(STATUS_FIXTURE);
117
+ const diagnostics = formatStatusDiagnosticsMarkdown(STATUS_FIXTURE);
118
+ for (const value of ["75.0%", "65.0%", "1h (config for anthropic/claude-opus-5)"]) {
119
+ expect(summary).toContain(value);
120
+ expect(diagnostics).toContain(value);
121
+ }
122
+ });
123
+
124
+ test("keeps the previous OpenCode detail behind diagnostics", () => {
125
+ const diagnostics = formatStatusDiagnosticsMarkdown(STATUS_FIXTURE);
126
+ expect(diagnostics).toContain("- **Active profile:** work");
127
+ expect(diagnostics).toContain("- **Tags:** 4 active, 1 dropped; 2 pending drops");
128
+ expect(diagnostics).toContain("- **Execute threshold:** 65.0%");
129
+ });
130
+
131
+ test("keeps internal vocabulary and identifiers out of the summary", () => {
132
+ const summary = formatStatusDetailMarkdown({
89
133
  ...STATUS_FIXTURE,
134
+ sessionId: "session-secret",
135
+ projectIdentity: "/Users/example/secret-project",
90
136
  hostBackendsModuleSide: true,
137
+ lastTransformError: "facade MODULE drain failed in mc-store /tmp/private",
138
+ historianFailureCount: 2,
91
139
  });
92
- const tsStatus = formatStatusDetailMarkdown(STATUS_FIXTURE);
140
+ for (const forbidden of [
141
+ "session-secret",
142
+ "/Users/",
143
+ "tag counter",
144
+ "protection",
145
+ "formula",
146
+ "MODULE",
147
+ "mc-store",
148
+ "harness",
149
+ "compartment",
150
+ "facade",
151
+ "changefeed",
152
+ "drain",
153
+ ]) {
154
+ expect(summary.toLowerCase()).not.toContain(forbidden.toLowerCase());
155
+ }
156
+ expect(summary).toContain("(MC-S02)");
157
+ expect(summary).toContain("(MC-H01)");
158
+ });
93
159
 
94
- expect(rustStatus).toContain(
95
- "Host backends → MODULE: ctx_memory, ctx_note; historian: module-side",
96
- );
97
- expect(tsStatus).not.toContain("Host backends → MODULE");
160
+ test("does not expose module routing in the summary", () => {
161
+ const rustStatus = formatStatusDetailMarkdown({
162
+ ...STATUS_FIXTURE,
163
+ hostBackendsModuleSide: true,
164
+ });
165
+ expect(rustStatus).not.toContain("MODULE");
166
+ expect(rustStatus).not.toContain("mc-store");
98
167
  });
99
168
  });
@@ -9,6 +9,8 @@ import {
9
9
  } from "./opencode-db-path";
10
10
  import type { StatusDetail } from "./rpc-types";
11
11
  import { RUST_MODE_HOST_PATHS_LINE } from "./rust-mode-status";
12
+ import { renderUserStatusSummary, statusSummaryFromDetail } from "./status-summary";
13
+ import { renderUserFacingFailure } from "./user-facing-codes";
12
14
 
13
15
  function formatCount(value: number): string {
14
16
  return Math.round(value).toLocaleString();
@@ -21,8 +23,13 @@ function formatCacheLane(detail: StatusDetail): string {
21
23
  return `live (${Math.round(detail.cacheRemainingMs / 1000)}s remaining); TTL ${detail.cacheTtl}`;
22
24
  }
23
25
 
24
- /** Render the same StatusDetail payload that powers the TUI dialog for chat-only clients. */
26
+ /** Render the default user summary for chat-only OpenCode clients. */
25
27
  export function formatStatusDetailMarkdown(detail: StatusDetail): string {
28
+ return renderUserStatusSummary(statusSummaryFromDetail(detail), "markdown");
29
+ }
30
+
31
+ /** Render the opt-in operator detail that the status dialog exposes behind Diagnostics. */
32
+ export function formatStatusDiagnosticsMarkdown(detail: StatusDetail): string {
26
33
  const usableLimit =
27
34
  detail.contextLimit > 0
28
35
  ? `${formatCount(detail.contextLimit)} usable tokens`
@@ -71,7 +78,7 @@ export function formatStatusDetailMarkdown(detail: StatusDetail): string {
71
78
  );
72
79
  }
73
80
  if (detail.lastTransformError) {
74
- lines.push(`- **Last transform error:** ${detail.lastTransformError}`);
81
+ lines.push(`- **Warning:** ${renderUserFacingFailure("transform_update_failed")}`);
75
82
  }
76
83
 
77
84
  return lines.join("\n");
@@ -0,0 +1,167 @@
1
+ import { formatCacheTtlDisplay } from "./cache-ttl-display";
2
+ import type { StatusDetail } from "./rpc-types";
3
+ import { renderUserFacingFailure, type UserFacingFailureKey } from "./user-facing-codes";
4
+
5
+ export type StatusCompressionState = "off" | "compressing" | "ready" | "waiting";
6
+ export type StatusEmbeddingState = "off" | "running" | "paused" | "stopped" | "ready" | "waiting";
7
+
8
+ export interface UserStatusSummary {
9
+ inputTokens: number;
10
+ usableContextTokens: number;
11
+ usagePercentage: number;
12
+ cacheLifetime: string;
13
+ automaticCompressionThreshold: number | null;
14
+ compression: {
15
+ state: StatusCompressionState;
16
+ historyBlockCount: number;
17
+ };
18
+ reclaimable: {
19
+ toolOutputCount: number;
20
+ tokens: number;
21
+ };
22
+ memoryCount: number;
23
+ noteCount: number;
24
+ embedding: {
25
+ state: StatusEmbeddingState;
26
+ indexed: number;
27
+ total: number;
28
+ };
29
+ warnings: UserFacingFailureKey[];
30
+ }
31
+
32
+ export function statusSummaryFromDetail(detail: StatusDetail): UserStatusSummary {
33
+ const compressionState: StatusCompressionState =
34
+ detail.compaction_enabled === false
35
+ ? "off"
36
+ : detail.historianRunning ||
37
+ detail.compartmentInProgress ||
38
+ (detail.recompProgress?.phase === "recomp" &&
39
+ detail.recompProgress.kind !== "embed")
40
+ ? "compressing"
41
+ : detail.compartmentCount > 0
42
+ ? "ready"
43
+ : "waiting";
44
+ const warnings: UserFacingFailureKey[] = [];
45
+ if (detail.lastTransformError) warnings.push("transform_update_failed");
46
+ if ((detail.historianFailureCount ?? 0) > 0) warnings.push("historian_unavailable");
47
+ if ((detail.configParseFailures?.length ?? 0) > 0) warnings.push("configuration_warning");
48
+ if (detail.embedding?.state === "stopped") warnings.push("embedding_unavailable");
49
+ if ((detail.loggerDiagnostics?.swallowedWriteCount ?? 0) > 0) {
50
+ warnings.push("status_log_unavailable");
51
+ }
52
+
53
+ return {
54
+ inputTokens: detail.inputTokens,
55
+ usableContextTokens: detail.contextLimit,
56
+ usagePercentage: detail.usagePercentage,
57
+ cacheLifetime: formatCacheTtlDisplay({
58
+ value: detail.cacheTtl,
59
+ source: detail.cacheTtlSource ?? "session",
60
+ modelKey: detail.cacheTtlModelKey,
61
+ }).replace(/^Cache TTL:\s*/, ""),
62
+ automaticCompressionThreshold:
63
+ detail.compaction_enabled === false ? null : detail.executeThreshold,
64
+ compression: {
65
+ state: compressionState,
66
+ historyBlockCount: detail.compartmentCount,
67
+ },
68
+ reclaimable: {
69
+ toolOutputCount: detail.tailHygiene?.reclaimableToolOutputCount ?? 0,
70
+ tokens: detail.tailHygiene?.u ?? 0,
71
+ },
72
+ memoryCount: detail.memoryCount,
73
+ noteCount: (detail.sessionNoteCount ?? 0) + (detail.readySmartNoteCount ?? 0),
74
+ embedding: detail.embedding ?? {
75
+ state: "waiting",
76
+ indexed: 0,
77
+ total: 0,
78
+ },
79
+ warnings: [...new Set(warnings)],
80
+ };
81
+ }
82
+
83
+ function formatCount(value: number): string {
84
+ return Math.round(value).toLocaleString();
85
+ }
86
+
87
+ function compressionText(summary: UserStatusSummary): string {
88
+ const historyBlocks = `${summary.compression.historyBlockCount} history block${
89
+ summary.compression.historyBlockCount === 1 ? "" : "s"
90
+ }`;
91
+ switch (summary.compression.state) {
92
+ case "off":
93
+ return "Off";
94
+ case "compressing":
95
+ return `Compressing history · ${historyBlocks}`;
96
+ case "ready":
97
+ return `Ready · ${historyBlocks}`;
98
+ case "waiting":
99
+ return "Waiting for enough conversation history";
100
+ }
101
+ }
102
+
103
+ function reclaimableText(summary: UserStatusSummary): string {
104
+ const count = Math.max(0, Math.floor(summary.reclaimable.toolOutputCount));
105
+ if (count === 0) return "none";
106
+ const outputs = `${count} spent tool output${count === 1 ? "" : "s"}`;
107
+ return `${outputs} (~${Math.round(Math.max(0, summary.reclaimable.tokens) / 1000)}k tokens)`;
108
+ }
109
+
110
+ function embeddingText(summary: UserStatusSummary): string {
111
+ if (summary.embedding.state === "off") return "Off";
112
+ const coverage = `${formatCount(summary.embedding.indexed)} / ${formatCount(summary.embedding.total)} history blocks indexed`;
113
+ switch (summary.embedding.state) {
114
+ case "running":
115
+ return `Running · ${coverage}`;
116
+ case "paused":
117
+ return `Paused · ${coverage}`;
118
+ case "stopped":
119
+ return `Paused after an issue · ${coverage}`;
120
+ case "ready":
121
+ return `Ready · ${coverage}`;
122
+ case "waiting":
123
+ return `Waiting · ${coverage}`;
124
+ }
125
+ }
126
+
127
+ export function renderUserStatusSummary(
128
+ summary: UserStatusSummary,
129
+ style: "markdown" | "plain",
130
+ ): string {
131
+ const context = `${summary.usagePercentage.toFixed(1)}% of usable context (${formatCount(summary.inputTokens)} / ${
132
+ summary.usableContextTokens > 0 ? formatCount(summary.usableContextTokens) : "?"
133
+ } tokens)`;
134
+ const values = [
135
+ ["Context", context],
136
+ ["Cache lifetime", summary.cacheLifetime],
137
+ [
138
+ "Automatic compression",
139
+ summary.automaticCompressionThreshold === null
140
+ ? "Off"
141
+ : `at ${summary.automaticCompressionThreshold.toFixed(1)}% of usable context`,
142
+ ],
143
+ ["History compression", compressionText(summary)],
144
+ ["Reclaimable", reclaimableText(summary)],
145
+ [
146
+ "Memory",
147
+ `${formatCount(summary.memoryCount)} memories · ${formatCount(summary.noteCount)} notes`,
148
+ ],
149
+ ["Search indexing", embeddingText(summary)],
150
+ ] as const;
151
+ const lines =
152
+ style === "markdown"
153
+ ? [
154
+ "## Magic Context Status",
155
+ "",
156
+ ...values.map(([label, value]) => `- **${label}:** ${value}`),
157
+ ]
158
+ : ["Magic Context Status", ...values.map(([label, value]) => `${label}: ${value}`)];
159
+ for (const warning of summary.warnings) {
160
+ lines.push(
161
+ style === "markdown"
162
+ ? `- **Warning:** ${renderUserFacingFailure(warning)}`
163
+ : `Warning: ${renderUserFacingFailure(warning)}`,
164
+ );
165
+ }
166
+ return lines.join("\n");
167
+ }
@@ -9,6 +9,7 @@ export interface WireTailHygieneBaseline {
9
9
  generation_invalidated?: boolean;
10
10
  baseline_generation?: number;
11
11
  computed_at_ms?: number;
12
+ reclaimable_tool_output_count?: number;
12
13
  }
13
14
 
14
15
  function finiteNumber(value: unknown, fallback = 0): number {
@@ -34,6 +35,10 @@ export function resolveTailHygieneStatus(
34
35
  generationInvalidated: rustBaseline.generation_invalidated === true,
35
36
  baselineGeneration: Math.max(0, finiteNumber(rustBaseline.baseline_generation)),
36
37
  computedAt: Math.max(0, finiteNumber(rustBaseline.computed_at_ms)),
38
+ reclaimableToolOutputCount: Math.max(
39
+ 0,
40
+ Math.floor(finiteNumber(rustBaseline.reclaimable_tool_output_count)),
41
+ ),
37
42
  };
38
43
  }
39
44
  if (tsBaseline === undefined) return undefined;
@@ -47,6 +52,9 @@ export function resolveTailHygieneStatus(
47
52
  generationInvalidated: tsBaseline.generationInvalidated,
48
53
  baselineGeneration: Math.max(0, tsBaseline.baselineGeneration),
49
54
  computedAt: Math.max(0, tsBaseline.computedAt),
55
+ reclaimableToolOutputCount: tsBaseline.baselineParts.filter(
56
+ (part) => part.kind === "toolOutput" && part.uTokens > 0,
57
+ ).length,
50
58
  };
51
59
  }
52
60
 
@@ -0,0 +1,140 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import type { DreamRunFailureClass } from "../features/magic-context/dreamer/storage-dream-runs";
5
+ import type { EmbeddingFailureClass } from "../features/magic-context/memory/embedding-failure";
6
+ import {
7
+ type CapabilityRefusal,
8
+ dreamFailureCode,
9
+ embeddingFailureCode,
10
+ renderCapabilityRefusal,
11
+ renderDreamFailure,
12
+ renderEmbeddingFailure,
13
+ renderUserFacingFailure,
14
+ USER_FACING_FAILURES,
15
+ } from "./user-facing-codes";
16
+
17
+ const DREAM_CLASSES: DreamRunFailureClass[] = [
18
+ "provider_timeout",
19
+ "provider_error",
20
+ "empty_completion",
21
+ "no_models",
22
+ "child_aborted",
23
+ "parse_failed",
24
+ "unknown",
25
+ ];
26
+
27
+ const EMBEDDING_CLASSES: EmbeddingFailureClass[] = [
28
+ "substitution_rejected",
29
+ "http_error",
30
+ "transport_error",
31
+ "invalid_envelope",
32
+ "empty_result",
33
+ "certification_refusal",
34
+ "credential_required",
35
+ "local_binding_missing",
36
+ "local_fs_unavailable",
37
+ "local_download_failure",
38
+ "local_runtime_error",
39
+ ];
40
+
41
+ describe("user-facing failure codes", () => {
42
+ test("keeps the closed table unique and renders one calm sentence plus one action", () => {
43
+ const entries = Object.values(USER_FACING_FAILURES);
44
+ expect(new Set(entries.map((entry) => entry.code)).size).toBe(entries.length);
45
+ for (const [key, entry] of Object.entries(USER_FACING_FAILURES)) {
46
+ expect(entry.code).toMatch(/^MC-[CHDERS]\d{2}$/);
47
+ expect(entry.sentence).toEndWith(".");
48
+ expect(entry.action).toEndWith(".");
49
+ expect(renderUserFacingFailure(key as keyof typeof USER_FACING_FAILURES)).toBe(
50
+ `${entry.sentence} ${entry.action} (${entry.code})`,
51
+ );
52
+ }
53
+ });
54
+
55
+ test("uses actionable command and configuration guidance in markdown and plain text", () => {
56
+ expect(renderEmbeddingFailure("local_binding_missing")).toContain(
57
+ "Run `npx @cortexkit/magic-context doctor`, then retry. (MC-E08)",
58
+ );
59
+ expect(renderEmbeddingFailure("local_runtime_error", "plain")).toContain(
60
+ "Run npx @cortexkit/magic-context doctor, then retry. (MC-E11)",
61
+ );
62
+ expect(renderEmbeddingFailure("local_runtime_error", "plain")).not.toContain("`");
63
+ expect(renderEmbeddingFailure("certification_refusal")).toContain(
64
+ "Finish the provider setup, or set a fallback provider in the embedding settings, then run /ctx-embed start again. (MC-E06)",
65
+ );
66
+ expect(renderUserFacingFailure("configuration_warning")).toContain(
67
+ "Fix the configuration warning shown in /ctx-status diagnostics, then restart. (MC-S03)",
68
+ );
69
+ expect(renderUserFacingFailure("session_upgrade_unavailable")).toContain(
70
+ "Run /ctx-recomp instead. (MC-C07)",
71
+ );
72
+ });
73
+
74
+ test("maps every structured dream and embedding failure class", () => {
75
+ for (const failureClass of DREAM_CLASSES) {
76
+ expect(renderDreamFailure(failureClass)).toEndWith(
77
+ `(${dreamFailureCode(failureClass)})`,
78
+ );
79
+ }
80
+ for (const failureClass of EMBEDDING_CLASSES) {
81
+ expect(renderEmbeddingFailure(failureClass)).toEndWith(
82
+ `(${embeddingFailureCode(failureClass)})`,
83
+ );
84
+ }
85
+ });
86
+
87
+ test("renders every capability refusal without internal vocabulary", () => {
88
+ const capabilities: CapabilityRefusal[] = [
89
+ "memory_write",
90
+ "memory_access",
91
+ "note_change",
92
+ "note_access",
93
+ "context_cleanup",
94
+ "partial_history",
95
+ "session_upgrade",
96
+ "smart_note_condition",
97
+ "history_compression",
98
+ "context_service",
99
+ ];
100
+ for (const capability of capabilities) {
101
+ const rendered = renderCapabilityRefusal(capability);
102
+ expect(rendered).toMatch(/\(MC-C\d{2}\)$/);
103
+ for (const forbidden of ["authority", "MODULE", "drain", "facade", "changefeed"]) {
104
+ expect(rendered).not.toContain(forbidden);
105
+ }
106
+ }
107
+ });
108
+
109
+ test("source fence keeps raw exceptions out of user-facing reply builders", () => {
110
+ const builders = [
111
+ "hooks/magic-context/command-handler.ts",
112
+ "hooks/magic-context/compartment-runner-validation.ts",
113
+ "hooks/magic-context/compartment-runner-recomp.ts",
114
+ "hooks/magic-context/compartment-runner-partial-recomp.ts",
115
+ "hooks/magic-context/format-embed-failure.ts",
116
+ "hooks/magic-context/recomp-orchestrator.ts",
117
+ "shared/status-detail-text.ts",
118
+ "../../pi-plugin/src/commands/ctx-dream.ts",
119
+ "../../pi-plugin/src/commands/ctx-status.ts",
120
+ ];
121
+ const allowedLogFragments = ["command notification delivery failed", "/ctx-dream failed"];
122
+ const violations: string[] = [];
123
+ for (const relativePath of builders) {
124
+ const path = resolve(import.meta.dir, "..", relativePath);
125
+ const lines = readFileSync(path, "utf8").split("\n");
126
+ lines.forEach((line, index) => {
127
+ if (
128
+ !/(error\.message|String\((?:err|error)\)|describeError\([^)]*\)\.brief)/.test(
129
+ line,
130
+ )
131
+ ) {
132
+ return;
133
+ }
134
+ if (allowedLogFragments.some((fragment) => line.includes(fragment))) return;
135
+ violations.push(`${relativePath}:${index + 1}: ${line.trim()}`);
136
+ });
137
+ }
138
+ expect(violations).toEqual([]);
139
+ });
140
+ });