memorix 1.4.3 → 1.4.4

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 (144) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +11 -4
  3. package/README.zh-CN.md +7 -1
  4. package/TEAM.md +86 -86
  5. package/assets/star-history-dark.svg +8 -8
  6. package/assets/star-history-light.svg +8 -8
  7. package/assets/star-history.json +16 -2
  8. package/dist/cli/index.js +194 -109
  9. package/dist/cli/memcode.js +0 -0
  10. package/dist/index.js +39 -12
  11. package/dist/maintenance-runner.js +9 -7
  12. package/dist/media-audio-runner.js +9 -7
  13. package/dist/media-video-runner.js +9 -7
  14. package/dist/memcode/index.js +9 -7
  15. package/dist/memcode-runtime/CHANGELOG.md +19 -0
  16. package/dist/sdk.js +38 -11
  17. package/dist/vector-backfill-runner.js +9 -7
  18. package/docs/AGENT_OPERATOR_PLAYBOOK.md +3 -3
  19. package/docs/API_REFERENCE.md +9 -5
  20. package/docs/CONFIGURATION.md +15 -5
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/DEVELOPMENT.md +2 -2
  23. package/docs/KNOWN_ISSUES_AND_ROADMAP.md +3 -3
  24. package/docs/MODULES.md +1 -1
  25. package/docs/README.md +1 -1
  26. package/docs/SETUP.md +3 -3
  27. package/docs/hooks-architecture.md +2 -1
  28. package/llms-full.txt +4 -4
  29. package/llms.txt +2 -2
  30. package/package.json +1 -1
  31. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  32. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  33. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  34. package/plugins/copilot/memorix/plugin.json +1 -1
  35. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  36. package/plugins/omp/memorix/package.json +1 -1
  37. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  38. package/plugins/pi/memorix/package.json +1 -1
  39. package/server.json +2 -2
  40. package/src/audit/index.ts +156 -156
  41. package/src/cli/commands/audit-list.ts +89 -89
  42. package/src/cli/commands/formation.ts +48 -48
  43. package/src/cli/commands/git-hook-install.ts +111 -111
  44. package/src/cli/commands/handoff.ts +54 -54
  45. package/src/cli/commands/hooks-status.ts +63 -63
  46. package/src/cli/commands/ingest.ts +44 -44
  47. package/src/cli/commands/integrate-shared.ts +15 -15
  48. package/src/cli/commands/lock.ts +82 -82
  49. package/src/cli/commands/media-image.ts +27 -0
  50. package/src/cli/commands/media.ts +8 -1
  51. package/src/cli/commands/memory.ts +44 -34
  52. package/src/cli/commands/message.ts +104 -104
  53. package/src/cli/commands/operator-shared.ts +62 -18
  54. package/src/cli/commands/poll.ts +58 -58
  55. package/src/cli/commands/purge-all-memory.ts +85 -85
  56. package/src/cli/commands/purge-project-memory.ts +83 -83
  57. package/src/cli/commands/reasoning.ts +118 -118
  58. package/src/cli/commands/skills.ts +114 -114
  59. package/src/cli/commands/task.ts +167 -167
  60. package/src/cli/commands/transfer.ts +47 -47
  61. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  62. package/src/cli/tui/ChatView.tsx +234 -234
  63. package/src/cli/tui/CommandBar.tsx +312 -312
  64. package/src/cli/tui/ContextRail.tsx +118 -118
  65. package/src/cli/tui/HeaderBar.tsx +72 -72
  66. package/src/cli/tui/LogoBanner.tsx +51 -51
  67. package/src/cli/tui/Sidebar.tsx +179 -179
  68. package/src/cli/tui/index.ts +41 -41
  69. package/src/cli/tui/markdown-render.tsx +371 -371
  70. package/src/cli/tui/use-mouse.ts +157 -157
  71. package/src/cli/tui/useNavigation.ts +56 -56
  72. package/src/cli/version.ts +7 -7
  73. package/src/cli/workbench.ts +1 -1
  74. package/src/dashboard/project-classification.ts +64 -64
  75. package/src/git/hooks-path.ts +85 -85
  76. package/src/hooks/pattern-detector.ts +173 -173
  77. package/src/hooks/significance-filter.ts +250 -250
  78. package/src/llm/memory-manager.ts +328 -328
  79. package/src/llm/provider.ts +885 -885
  80. package/src/llm/quality.ts +248 -248
  81. package/src/media/minimax.ts +9 -1
  82. package/src/memory/attribution-guard.ts +249 -249
  83. package/src/memory/disclosure-policy.ts +135 -135
  84. package/src/memory/entity-extractor.ts +197 -197
  85. package/src/memory/formation/evaluate.ts +217 -217
  86. package/src/memory/formation/extract.ts +361 -361
  87. package/src/memory/formation/index.ts +417 -417
  88. package/src/memory/formation/resolve.ts +344 -344
  89. package/src/memory/formation/types.ts +315 -315
  90. package/src/memory/freshness.ts +122 -122
  91. package/src/memory/graph.ts +197 -197
  92. package/src/memory/refs.ts +94 -94
  93. package/src/memory/secret-filter.ts +79 -79
  94. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  95. package/src/orchestrate/adapters/claude.ts +111 -111
  96. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  97. package/src/orchestrate/adapters/codex.ts +41 -41
  98. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  99. package/src/orchestrate/adapters/gemini.ts +42 -42
  100. package/src/orchestrate/adapters/index.ts +73 -73
  101. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  102. package/src/orchestrate/adapters/opencode.ts +47 -47
  103. package/src/orchestrate/adapters/types.ts +77 -77
  104. package/src/orchestrate/capability-router.ts +284 -284
  105. package/src/orchestrate/context-compact.ts +188 -188
  106. package/src/orchestrate/cost-tracker.ts +219 -219
  107. package/src/orchestrate/error-recovery.ts +191 -191
  108. package/src/orchestrate/evidence.ts +140 -140
  109. package/src/orchestrate/ledger.ts +110 -110
  110. package/src/orchestrate/memorix-bridge.ts +343 -343
  111. package/src/orchestrate/output-budget.ts +80 -80
  112. package/src/orchestrate/permission.ts +152 -152
  113. package/src/orchestrate/pipeline-trace.ts +131 -131
  114. package/src/orchestrate/prompt-builder.ts +155 -155
  115. package/src/orchestrate/ring-buffer.ts +37 -37
  116. package/src/orchestrate/task-graph.ts +389 -389
  117. package/src/project/aliases.ts +374 -374
  118. package/src/rules/adapters/trae.ts +56 -56
  119. package/src/rules/syncer.ts +235 -235
  120. package/src/search/query-expansion.ts +52 -52
  121. package/src/server/formation-timeout.ts +27 -27
  122. package/src/server.ts +23 -1
  123. package/src/skills/mini-skills.ts +386 -386
  124. package/src/store/chat-store.ts +119 -119
  125. package/src/store/graph-store.ts +249 -249
  126. package/src/store/mini-skill-store.ts +349 -349
  127. package/src/store/persistence-json.ts +212 -212
  128. package/src/store/persistence.ts +291 -291
  129. package/src/store/project-affinity.ts +195 -195
  130. package/src/team/event-bus.ts +76 -76
  131. package/src/team/file-locks.ts +173 -173
  132. package/src/team/handoff.ts +161 -161
  133. package/src/team/messages.ts +203 -203
  134. package/src/team/poll.ts +132 -132
  135. package/src/team/tasks.ts +211 -211
  136. package/src/workspace/mcp-adapters/codex.ts +191 -191
  137. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  138. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  139. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  140. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  141. package/src/workspace/mcp-adapters/trae.ts +134 -134
  142. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  143. package/src/workspace/sanitizer.ts +60 -60
  144. package/dist/memcode-runtime/docs/images/exy.png +0 -0
@@ -1,188 +1,188 @@
1
- /**
2
- * Context Compaction — Phase 7, Step 6: 4-layer compaction for long pipelines.
3
- *
4
- * Prevents context overflow by progressively compacting older content:
5
- * Layer 1: Tool Result Micro-Compact — trim old ledger entries to short placeholders
6
- * Layer 2: Gate Output Compact — handled by output-budget.ts (trimAndPersist)
7
- * Layer 3: Fix Prompt Compaction — summarize previous fix attempts
8
- * Layer 4: Pipeline-Level Compaction — compact old tasks in long pipelines
9
- *
10
- * Circuit Breaker: if compaction fails, fall back to simple truncation
11
- * (first 500 + last 500 chars). Never let compaction crash the pipeline.
12
- */
13
-
14
- import { trimToBudget } from './output-budget.js';
15
-
16
- // ── Types ──────────────────────────────────────────────────────────
17
-
18
- export interface CompactionConfig {
19
- /** Max number of full ledger entries to keep (older ones get compacted). Default: 5 */
20
- maxFullEntries: number;
21
- /** Max total chars for all fix attempt summaries. Default: 2000 */
22
- fixHistoryBudget: number;
23
- /** Pipeline task count threshold for pipeline-level compaction. Default: 20 */
24
- pipelineCompactThreshold: number;
25
- /** Number of recent tasks to keep in full detail during pipeline compaction. Default: 5 */
26
- recentTaskWindow: number;
27
- }
28
-
29
- export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
30
- maxFullEntries: 5,
31
- fixHistoryBudget: 2000,
32
- pipelineCompactThreshold: 20,
33
- recentTaskWindow: 5,
34
- };
35
-
36
- export interface LedgerEntry {
37
- taskId: string;
38
- role: string;
39
- agent: string;
40
- status: 'completed' | 'failed';
41
- summary: string;
42
- durationMs: number;
43
- }
44
-
45
- export interface FixAttemptRecord {
46
- attempt: number;
47
- gate: string;
48
- errorOutput: string;
49
- fixOutput: string;
50
- passed: boolean;
51
- }
52
-
53
- // ── Layer 1: Tool Result Micro-Compact ─────────────────────────────
54
-
55
- /**
56
- * Compact old ledger entries: keep only the N most recent in full detail,
57
- * replace older ones with one-line placeholders.
58
- *
59
- * Never throws — returns input unchanged on error.
60
- */
61
- export function compactLedgerEntries(
62
- entries: LedgerEntry[],
63
- config: CompactionConfig = DEFAULT_COMPACTION_CONFIG,
64
- ): string {
65
- try {
66
- if (entries.length <= config.maxFullEntries) {
67
- return entries.map(formatFullEntry).join('\n');
68
- }
69
-
70
- const oldEntries = entries.slice(0, entries.length - config.maxFullEntries);
71
- const recentEntries = entries.slice(-config.maxFullEntries);
72
-
73
- const compactedOld = oldEntries.map(e => {
74
- const status = e.status === 'completed' ? '[OK]' : '[ERROR]';
75
- return ` ${status} ${e.role}/${e.agent}: ${e.summary.slice(0, 60)}`;
76
- }).join('\n');
77
-
78
- const compactedHeader = `### Earlier tasks (${oldEntries.length} compacted)\n${compactedOld}`;
79
- const recentSection = `### Recent tasks (${recentEntries.length} full)\n${recentEntries.map(formatFullEntry).join('\n')}`;
80
-
81
- return `${compactedHeader}\n\n${recentSection}`;
82
- } catch {
83
- // Circuit breaker: return raw entries on failure
84
- return entries.map(formatFullEntry).join('\n');
85
- }
86
- }
87
-
88
- // ── Layer 3: Fix Prompt Compaction ─────────────────────────────────
89
-
90
- /**
91
- * Compact fix attempt history for inclusion in the next fix prompt.
92
- * After the first attempt, summarize previous attempts instead of
93
- * including full error + fix outputs each time.
94
- *
95
- * Never throws — returns simple concatenation on error.
96
- */
97
- export function compactFixHistory(
98
- attempts: FixAttemptRecord[],
99
- config: CompactionConfig = DEFAULT_COMPACTION_CONFIG,
100
- ): string {
101
- try {
102
- if (attempts.length === 0) return '';
103
-
104
- // First attempt: no history needed
105
- if (attempts.length === 1) {
106
- return formatFixAttempt(attempts[0]);
107
- }
108
-
109
- // Multiple attempts: summarize older, keep latest full
110
- const older = attempts.slice(0, -1);
111
- const latest = attempts[attempts.length - 1];
112
-
113
- const olderSummary = older.map((a, i) => {
114
- const status = a.passed ? 'PASSED' : 'FAILED';
115
- return ` - Attempt ${i + 1}: ${status} (${a.gate}) — ${a.errorOutput.slice(0, 80)}`;
116
- }).join('\n');
117
-
118
- let history = `### Previous fix attempts (${older.length} summarized)\n${olderSummary}`;
119
-
120
- // Enforce budget on history section
121
- if (history.length > config.fixHistoryBudget) {
122
- history = trimToBudget(history, config.fixHistoryBudget);
123
- }
124
-
125
- return `${history}\n\n### Latest attempt\n${formatFixAttempt(latest)}`;
126
- } catch {
127
- // Circuit breaker: simple concatenation
128
- return attempts.map(formatFixAttempt).join('\n---\n');
129
- }
130
- }
131
-
132
- // ── Layer 4: Pipeline-Level Compaction ─────────────────────────────
133
-
134
- /**
135
- * Compact an entire pipeline's task list for prompt injection.
136
- * For long pipelines (>threshold tasks), compact older entries into
137
- * a summary paragraph, keeping only the recent N tasks in full.
138
- *
139
- * Never throws — returns full list on error.
140
- */
141
- export function compactPipelineContext(
142
- entries: LedgerEntry[],
143
- config: CompactionConfig = DEFAULT_COMPACTION_CONFIG,
144
- ): string {
145
- try {
146
- if (entries.length <= config.pipelineCompactThreshold) {
147
- return compactLedgerEntries(entries, config);
148
- }
149
-
150
- const oldEntries = entries.slice(0, entries.length - config.recentTaskWindow);
151
- const recentEntries = entries.slice(-config.recentTaskWindow);
152
-
153
- // Summary stats for old entries
154
- const completedCount = oldEntries.filter(e => e.status === 'completed').length;
155
- const failedCount = oldEntries.filter(e => e.status === 'failed').length;
156
- const roles = [...new Set(oldEntries.map(e => e.role))];
157
-
158
- const summary = [
159
- `### Pipeline progress (${entries.length} total tasks)`,
160
- `${oldEntries.length} earlier tasks: ${completedCount} completed, ${failedCount} failed.`,
161
- `Roles involved: ${roles.join(', ')}.`,
162
- ].join('\n');
163
-
164
- const recentSection = `### Recent tasks (${recentEntries.length} full detail)\n${recentEntries.map(formatFullEntry).join('\n')}`;
165
-
166
- return `${summary}\n\n${recentSection}`;
167
- } catch {
168
- // Circuit breaker: return full list via basic compaction
169
- return compactLedgerEntries(entries, config);
170
- }
171
- }
172
-
173
- // ── Helpers ────────────────────────────────────────────────────────
174
-
175
- function formatFullEntry(e: LedgerEntry): string {
176
- const status = e.status === 'completed' ? '[OK]' : '[ERROR]';
177
- const duration = `${(e.durationMs / 1000).toFixed(1)}s`;
178
- return `- [${status}] **${e.role}** (${e.agent}, ${duration}): ${e.summary}`;
179
- }
180
-
181
- function formatFixAttempt(a: FixAttemptRecord): string {
182
- const status = a.passed ? 'PASSED' : 'FAILED';
183
- return [
184
- `**Attempt ${a.attempt}** [${status}] — ${a.gate} gate`,
185
- `Error: ${a.errorOutput.slice(0, 300)}`,
186
- `Fix: ${a.fixOutput.slice(0, 300)}`,
187
- ].join('\n');
188
- }
1
+ /**
2
+ * Context Compaction — Phase 7, Step 6: 4-layer compaction for long pipelines.
3
+ *
4
+ * Prevents context overflow by progressively compacting older content:
5
+ * Layer 1: Tool Result Micro-Compact — trim old ledger entries to short placeholders
6
+ * Layer 2: Gate Output Compact — handled by output-budget.ts (trimAndPersist)
7
+ * Layer 3: Fix Prompt Compaction — summarize previous fix attempts
8
+ * Layer 4: Pipeline-Level Compaction — compact old tasks in long pipelines
9
+ *
10
+ * Circuit Breaker: if compaction fails, fall back to simple truncation
11
+ * (first 500 + last 500 chars). Never let compaction crash the pipeline.
12
+ */
13
+
14
+ import { trimToBudget } from './output-budget.js';
15
+
16
+ // ── Types ──────────────────────────────────────────────────────────
17
+
18
+ export interface CompactionConfig {
19
+ /** Max number of full ledger entries to keep (older ones get compacted). Default: 5 */
20
+ maxFullEntries: number;
21
+ /** Max total chars for all fix attempt summaries. Default: 2000 */
22
+ fixHistoryBudget: number;
23
+ /** Pipeline task count threshold for pipeline-level compaction. Default: 20 */
24
+ pipelineCompactThreshold: number;
25
+ /** Number of recent tasks to keep in full detail during pipeline compaction. Default: 5 */
26
+ recentTaskWindow: number;
27
+ }
28
+
29
+ export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
30
+ maxFullEntries: 5,
31
+ fixHistoryBudget: 2000,
32
+ pipelineCompactThreshold: 20,
33
+ recentTaskWindow: 5,
34
+ };
35
+
36
+ export interface LedgerEntry {
37
+ taskId: string;
38
+ role: string;
39
+ agent: string;
40
+ status: 'completed' | 'failed';
41
+ summary: string;
42
+ durationMs: number;
43
+ }
44
+
45
+ export interface FixAttemptRecord {
46
+ attempt: number;
47
+ gate: string;
48
+ errorOutput: string;
49
+ fixOutput: string;
50
+ passed: boolean;
51
+ }
52
+
53
+ // ── Layer 1: Tool Result Micro-Compact ─────────────────────────────
54
+
55
+ /**
56
+ * Compact old ledger entries: keep only the N most recent in full detail,
57
+ * replace older ones with one-line placeholders.
58
+ *
59
+ * Never throws — returns input unchanged on error.
60
+ */
61
+ export function compactLedgerEntries(
62
+ entries: LedgerEntry[],
63
+ config: CompactionConfig = DEFAULT_COMPACTION_CONFIG,
64
+ ): string {
65
+ try {
66
+ if (entries.length <= config.maxFullEntries) {
67
+ return entries.map(formatFullEntry).join('\n');
68
+ }
69
+
70
+ const oldEntries = entries.slice(0, entries.length - config.maxFullEntries);
71
+ const recentEntries = entries.slice(-config.maxFullEntries);
72
+
73
+ const compactedOld = oldEntries.map(e => {
74
+ const status = e.status === 'completed' ? '[OK]' : '[ERROR]';
75
+ return ` ${status} ${e.role}/${e.agent}: ${e.summary.slice(0, 60)}`;
76
+ }).join('\n');
77
+
78
+ const compactedHeader = `### Earlier tasks (${oldEntries.length} compacted)\n${compactedOld}`;
79
+ const recentSection = `### Recent tasks (${recentEntries.length} full)\n${recentEntries.map(formatFullEntry).join('\n')}`;
80
+
81
+ return `${compactedHeader}\n\n${recentSection}`;
82
+ } catch {
83
+ // Circuit breaker: return raw entries on failure
84
+ return entries.map(formatFullEntry).join('\n');
85
+ }
86
+ }
87
+
88
+ // ── Layer 3: Fix Prompt Compaction ─────────────────────────────────
89
+
90
+ /**
91
+ * Compact fix attempt history for inclusion in the next fix prompt.
92
+ * After the first attempt, summarize previous attempts instead of
93
+ * including full error + fix outputs each time.
94
+ *
95
+ * Never throws — returns simple concatenation on error.
96
+ */
97
+ export function compactFixHistory(
98
+ attempts: FixAttemptRecord[],
99
+ config: CompactionConfig = DEFAULT_COMPACTION_CONFIG,
100
+ ): string {
101
+ try {
102
+ if (attempts.length === 0) return '';
103
+
104
+ // First attempt: no history needed
105
+ if (attempts.length === 1) {
106
+ return formatFixAttempt(attempts[0]);
107
+ }
108
+
109
+ // Multiple attempts: summarize older, keep latest full
110
+ const older = attempts.slice(0, -1);
111
+ const latest = attempts[attempts.length - 1];
112
+
113
+ const olderSummary = older.map((a, i) => {
114
+ const status = a.passed ? 'PASSED' : 'FAILED';
115
+ return ` - Attempt ${i + 1}: ${status} (${a.gate}) — ${a.errorOutput.slice(0, 80)}`;
116
+ }).join('\n');
117
+
118
+ let history = `### Previous fix attempts (${older.length} summarized)\n${olderSummary}`;
119
+
120
+ // Enforce budget on history section
121
+ if (history.length > config.fixHistoryBudget) {
122
+ history = trimToBudget(history, config.fixHistoryBudget);
123
+ }
124
+
125
+ return `${history}\n\n### Latest attempt\n${formatFixAttempt(latest)}`;
126
+ } catch {
127
+ // Circuit breaker: simple concatenation
128
+ return attempts.map(formatFixAttempt).join('\n---\n');
129
+ }
130
+ }
131
+
132
+ // ── Layer 4: Pipeline-Level Compaction ─────────────────────────────
133
+
134
+ /**
135
+ * Compact an entire pipeline's task list for prompt injection.
136
+ * For long pipelines (>threshold tasks), compact older entries into
137
+ * a summary paragraph, keeping only the recent N tasks in full.
138
+ *
139
+ * Never throws — returns full list on error.
140
+ */
141
+ export function compactPipelineContext(
142
+ entries: LedgerEntry[],
143
+ config: CompactionConfig = DEFAULT_COMPACTION_CONFIG,
144
+ ): string {
145
+ try {
146
+ if (entries.length <= config.pipelineCompactThreshold) {
147
+ return compactLedgerEntries(entries, config);
148
+ }
149
+
150
+ const oldEntries = entries.slice(0, entries.length - config.recentTaskWindow);
151
+ const recentEntries = entries.slice(-config.recentTaskWindow);
152
+
153
+ // Summary stats for old entries
154
+ const completedCount = oldEntries.filter(e => e.status === 'completed').length;
155
+ const failedCount = oldEntries.filter(e => e.status === 'failed').length;
156
+ const roles = [...new Set(oldEntries.map(e => e.role))];
157
+
158
+ const summary = [
159
+ `### Pipeline progress (${entries.length} total tasks)`,
160
+ `${oldEntries.length} earlier tasks: ${completedCount} completed, ${failedCount} failed.`,
161
+ `Roles involved: ${roles.join(', ')}.`,
162
+ ].join('\n');
163
+
164
+ const recentSection = `### Recent tasks (${recentEntries.length} full detail)\n${recentEntries.map(formatFullEntry).join('\n')}`;
165
+
166
+ return `${summary}\n\n${recentSection}`;
167
+ } catch {
168
+ // Circuit breaker: return full list via basic compaction
169
+ return compactLedgerEntries(entries, config);
170
+ }
171
+ }
172
+
173
+ // ── Helpers ────────────────────────────────────────────────────────
174
+
175
+ function formatFullEntry(e: LedgerEntry): string {
176
+ const status = e.status === 'completed' ? '[OK]' : '[ERROR]';
177
+ const duration = `${(e.durationMs / 1000).toFixed(1)}s`;
178
+ return `- [${status}] **${e.role}** (${e.agent}, ${duration}): ${e.summary}`;
179
+ }
180
+
181
+ function formatFixAttempt(a: FixAttemptRecord): string {
182
+ const status = a.passed ? 'PASSED' : 'FAILED';
183
+ return [
184
+ `**Attempt ${a.attempt}** [${status}] — ${a.gate} gate`,
185
+ `Error: ${a.errorOutput.slice(0, 300)}`,
186
+ `Fix: ${a.fixOutput.slice(0, 300)}`,
187
+ ].join('\n');
188
+ }