jorgex-stack 1.0.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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/PRD.md +297 -0
  3. package/README.md +58 -0
  4. package/dist/cli.js +2894 -0
  5. package/package.json +51 -0
  6. package/stack/agents/README.md +33 -0
  7. package/stack/agents/backend-analyst.md +61 -0
  8. package/stack/agents/code-reviewer.md +66 -0
  9. package/stack/agents/code-simplifier.md +81 -0
  10. package/stack/agents/comment-fixer.md +54 -0
  11. package/stack/agents/docs-maintainer.md +88 -0
  12. package/stack/agents/engram.md +124 -0
  13. package/stack/agents/frontend-analyst.md +51 -0
  14. package/stack/agents/implementer.md +55 -0
  15. package/stack/agents/orchestrator.md +192 -0
  16. package/stack/agents/security-auditor.md +66 -0
  17. package/stack/agents/silent-failure-hunter.md +167 -0
  18. package/stack/agents/test-analyzer.md +91 -0
  19. package/stack/agents/tester.md +71 -0
  20. package/stack/agents/translator.md +101 -0
  21. package/stack/agents/type-design-analyzer.md +128 -0
  22. package/stack/commands/xreview.md +80 -0
  23. package/stack/config/defaults.json +37 -0
  24. package/stack/hooks/hooks.json +18 -0
  25. package/stack/mcp/servers.json +19 -0
  26. package/stack/plugins/opencode/engram.ts +378 -0
  27. package/stack/plugins/opencode/hooks.ts +766 -0
  28. package/stack/plugins/opencode/package.json +10 -0
  29. package/stack/plugins/opencode/worktree.ts +405 -0
  30. package/stack/scripts/post-pr-review.cjs +156 -0
  31. package/stack/skills/agent-browser/SKILL.md +55 -0
  32. package/stack/skills/agent-delegation/SKILL.md +58 -0
  33. package/stack/skills/deploy-to-vercel/SKILL.md +296 -0
  34. package/stack/skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
  35. package/stack/skills/deploy-to-vercel/resources/deploy.sh +301 -0
  36. package/stack/skills/diagnose/SKILL.md +117 -0
  37. package/stack/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
  38. package/stack/skills/find-skills/SKILL.md +133 -0
  39. package/stack/skills/graphify/.graphify_version +1 -0
  40. package/stack/skills/graphify/SKILL.md +1319 -0
  41. package/stack/skills/mcp-builder/LICENSE.txt +202 -0
  42. package/stack/skills/mcp-builder/SKILL.md +236 -0
  43. package/stack/skills/mcp-builder/reference/evaluation.md +602 -0
  44. package/stack/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  45. package/stack/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  46. package/stack/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  47. package/stack/skills/mcp-builder/scripts/connections.py +151 -0
  48. package/stack/skills/mcp-builder/scripts/evaluation.py +373 -0
  49. package/stack/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  50. package/stack/skills/mcp-builder/scripts/requirements.txt +2 -0
  51. package/stack/skills/obsidian-cli/SKILL.md +106 -0
  52. package/stack/skills/obsidian-markdown/SKILL.md +196 -0
  53. package/stack/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
  54. package/stack/skills/obsidian-markdown/references/EMBEDS.md +63 -0
  55. package/stack/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
  56. package/stack/skills/react-doctor/SKILL.md +19 -0
  57. package/stack/skills/skill-creator/LICENSE.txt +202 -0
  58. package/stack/skills/skill-creator/SKILL.md +485 -0
  59. package/stack/skills/skill-creator/agents/analyzer.md +274 -0
  60. package/stack/skills/skill-creator/agents/comparator.md +202 -0
  61. package/stack/skills/skill-creator/agents/grader.md +223 -0
  62. package/stack/skills/skill-creator/assets/eval_review.html +146 -0
  63. package/stack/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  64. package/stack/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  65. package/stack/skills/skill-creator/references/schemas.md +430 -0
  66. package/stack/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  67. package/stack/skills/skill-creator/scripts/generate_report.py +326 -0
  68. package/stack/skills/skill-creator/scripts/improve_description.py +248 -0
  69. package/stack/skills/skill-creator/scripts/package_skill.py +136 -0
  70. package/stack/skills/skill-creator/scripts/quick_validate.py +103 -0
  71. package/stack/skills/skill-creator/scripts/run_eval.py +310 -0
  72. package/stack/skills/skill-creator/scripts/run_loop.py +332 -0
  73. package/stack/skills/skill-creator/scripts/utils.py +47 -0
  74. package/stack/skills/supabase/SKILL.md +135 -0
  75. package/stack/skills/supabase/assets/feedback-issue-template.md +17 -0
  76. package/stack/skills/supabase/references/skill-feedback.md +17 -0
  77. package/stack/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  78. package/stack/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
  79. package/stack/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
  80. package/stack/skills/supabase-postgres-best-practices/references/_template.md +34 -0
  81. package/stack/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  82. package/stack/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  83. package/stack/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  84. package/stack/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  85. package/stack/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  86. package/stack/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  87. package/stack/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  88. package/stack/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  89. package/stack/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  90. package/stack/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  91. package/stack/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  92. package/stack/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  93. package/stack/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  94. package/stack/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  95. package/stack/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  96. package/stack/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  97. package/stack/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  98. package/stack/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  99. package/stack/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  100. package/stack/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
  101. package/stack/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  102. package/stack/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  103. package/stack/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
  104. package/stack/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  105. package/stack/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  106. package/stack/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  107. package/stack/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  108. package/stack/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  109. package/stack/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  110. package/stack/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  111. package/stack/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
  112. package/stack/skills/tdd/SKILL.md +109 -0
  113. package/stack/skills/tdd/deep-modules.md +33 -0
  114. package/stack/skills/tdd/interface-design.md +31 -0
  115. package/stack/skills/tdd/mocking.md +59 -0
  116. package/stack/skills/tdd/refactoring.md +10 -0
  117. package/stack/skills/tdd/tests.md +61 -0
  118. package/stack/skills/to-issues/SKILL.md +83 -0
  119. package/stack/skills/to-prd/SKILL.md +72 -0
  120. package/stack/skills/work-lifecycle/SKILL.md +79 -0
  121. package/stack/skills/work-lifecycle/references/plan-template.md +185 -0
  122. package/stack/system-prompt/AGENTS.md +171 -0
  123. package/stack/system-prompt/engram-protocol.md +53 -0
  124. package/upstreams.json +96 -0
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Engram — OpenCode plugin adapter
3
+ *
4
+ * Thin layer that connects OpenCode's event system to the Engram Go binary.
5
+ * The Go binary runs as a local HTTP server and handles all persistence.
6
+ *
7
+ * Flow:
8
+ * OpenCode events → this plugin → HTTP calls → engram serve → SQLite
9
+ *
10
+ * Session resilience:
11
+ * Uses `ensureSession()` before any DB write. This means sessions are
12
+ * created on-demand — even if the plugin was loaded after the session
13
+ * started (restart, reconnect, etc.). The session ID comes from OpenCode's
14
+ * hooks (input.sessionID) rather than relying on a session.created event.
15
+ */
16
+
17
+ import type { Plugin } from "@opencode-ai/plugin"
18
+
19
+ // ─── Configuration ───────────────────────────────────────────────────────────
20
+
21
+ const ENGRAM_PORT = parseInt(process.env.ENGRAM_PORT ?? "7437")
22
+ const ENGRAM_URL = `http://127.0.0.1:${ENGRAM_PORT}`
23
+ // "{{ENGRAM_BIN}}" lo resuelve el instalador con el binario detectado (D7).
24
+ const ENGRAM_BIN = process.env.ENGRAM_BIN ?? Bun.which("engram") ?? "{{ENGRAM_BIN}}"
25
+
26
+ // Engram's own MCP tools — don't count these as "tool calls" for session stats
27
+ const ENGRAM_TOOLS = new Set([
28
+ "mem_search",
29
+ "mem_save",
30
+ "mem_update",
31
+ "mem_delete",
32
+ "mem_suggest_topic_key",
33
+ "mem_save_prompt",
34
+ "mem_session_summary",
35
+ "mem_context",
36
+ "mem_stats",
37
+ "mem_timeline",
38
+ "mem_get_observation",
39
+ "mem_session_start",
40
+ "mem_session_end",
41
+ ])
42
+
43
+ // ─── Memory Instructions ─────────────────────────────────────────────────────
44
+ // These get injected into the agent's context so it knows to call mem_save.
45
+ // El placeholder ENGRAM_PROTOCOL lo resuelve el instalador con el contenido
46
+ // del protocolo canónico (stack/system-prompt/engram-protocol.md) — única
47
+ // fuente, alineada automáticamente con lo que reciben Claude Code y Codex.
48
+
49
+ const MEMORY_INSTRUCTIONS = "{{ENGRAM_PROTOCOL}}"
50
+
51
+ // ─── HTTP Client ─────────────────────────────────────────────────────────────
52
+
53
+ async function engramFetch(
54
+ path: string,
55
+ opts: { method?: string; body?: any } = {}
56
+ ): Promise<any> {
57
+ try {
58
+ const res = await fetch(`${ENGRAM_URL}${path}`, {
59
+ method: opts.method ?? "GET",
60
+ headers: opts.body ? { "Content-Type": "application/json" } : undefined,
61
+ body: opts.body ? JSON.stringify(opts.body) : undefined,
62
+ })
63
+ return await res.json()
64
+ } catch {
65
+ // Engram server not running — silently fail
66
+ return null
67
+ }
68
+ }
69
+
70
+ async function isEngramRunning(): Promise<boolean> {
71
+ try {
72
+ const res = await fetch(`${ENGRAM_URL}/health`, {
73
+ signal: AbortSignal.timeout(500),
74
+ })
75
+ return res.ok
76
+ } catch {
77
+ return false
78
+ }
79
+ }
80
+
81
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
82
+
83
+ function extractProjectName(directory: string): string {
84
+ // Try git remote origin URL
85
+ try {
86
+ const result = Bun.spawnSync(["git", "-C", directory, "remote", "get-url", "origin"])
87
+ if (result.exitCode === 0) {
88
+ const url = result.stdout?.toString().trim()
89
+ if (url) {
90
+ const name = url.replace(/\.git$/, "").split(/[/:]/).pop()
91
+ if (name) return name
92
+ }
93
+ }
94
+ } catch {}
95
+
96
+ // Fallback: git root directory name (works in worktrees)
97
+ try {
98
+ const result = Bun.spawnSync(["git", "-C", directory, "rev-parse", "--show-toplevel"])
99
+ if (result.exitCode === 0) {
100
+ const root = result.stdout?.toString().trim()
101
+ if (root) return root.split(/[\\/]/).pop() ?? "unknown"
102
+ }
103
+ } catch {}
104
+
105
+ // Final fallback: cwd basename
106
+ return directory.split(/[\\/]/).pop() ?? "unknown"
107
+ }
108
+
109
+ function truncate(str: string, max: number): string {
110
+ if (!str) return ""
111
+ return str.length > max ? str.slice(0, max) + "..." : str
112
+ }
113
+
114
+ /**
115
+ * Strip <private>...</private> tags before sending to engram.
116
+ * Double safety: the Go binary also strips, but we strip here too
117
+ * so sensitive data never even hits the wire.
118
+ */
119
+ function stripPrivateTags(str: string): string {
120
+ if (!str) return ""
121
+ return str.replace(/<private>[\s\S]*?<\/private>/gi, "[REDACTED]").trim()
122
+ }
123
+
124
+ // ─── Plugin Export ───────────────────────────────────────────────────────────
125
+
126
+ export const Engram: Plugin = async (ctx) => {
127
+ const oldProject = ctx.directory.split(/[\\/]/).pop() ?? "unknown"
128
+ const project = extractProjectName(ctx.directory)
129
+
130
+ // Track tool counts per session (in-memory only, not critical)
131
+ const toolCounts = new Map<string, number>()
132
+
133
+ // Track which sessions we've already ensured exist in engram
134
+ const knownSessions = new Set<string>()
135
+
136
+ // Track sub-agent session IDs so we can suppress their tool-hook registrations.
137
+ // Sub-agents (Task() calls) have a parentID or a title ending in " subagent)".
138
+ // We must not register them as top-level Engram sessions — they cause session
139
+ // inflation (e.g. 170 sessions for 1 real conversation, issue #116).
140
+ const subAgentSessions = new Set<string>()
141
+
142
+ /**
143
+ * Ensure a session exists in engram. Idempotent — calls POST /sessions
144
+ * which uses INSERT OR IGNORE. Safe to call multiple times.
145
+ *
146
+ * Silently skips sub-agent sessions (tracked in `subAgentSessions`).
147
+ */
148
+ async function ensureSession(sessionId: string): Promise<void> {
149
+ if (!sessionId || knownSessions.has(sessionId)) return
150
+ // Do not register sub-agent sessions in Engram (issue #116).
151
+ if (subAgentSessions.has(sessionId)) return
152
+ knownSessions.add(sessionId)
153
+ await engramFetch("/sessions", {
154
+ method: "POST",
155
+ body: {
156
+ id: sessionId,
157
+ project,
158
+ directory: ctx.directory,
159
+ },
160
+ })
161
+ }
162
+
163
+ // Try to start engram server if not running
164
+ const running = await isEngramRunning()
165
+ if (!running) {
166
+ try {
167
+ Bun.spawn([ENGRAM_BIN, "serve"], {
168
+ stdout: "ignore",
169
+ stderr: "ignore",
170
+ stdin: "ignore",
171
+ })
172
+ await new Promise((r) => setTimeout(r, 500))
173
+ } catch {
174
+ // Binary not found or can't start — plugin will silently no-op
175
+ }
176
+ }
177
+
178
+ // Migrate project name if it changed (one-time, idempotent)
179
+ // Must run AFTER server startup to ensure the endpoint is available
180
+ if (oldProject !== project) {
181
+ await engramFetch("/projects/migrate", {
182
+ method: "POST",
183
+ body: { old_project: oldProject, new_project: project },
184
+ })
185
+ }
186
+
187
+ // Auto-import: if .engram/manifest.json exists in the project repo,
188
+ // run `engram sync --import` to load any new chunks into the local DB.
189
+ // This is how git-synced memories get loaded when cloning a repo or
190
+ // pulling changes. Each chunk is imported only once (tracked by ID).
191
+ try {
192
+ const manifestFile = `${ctx.directory}/.engram/manifest.json`
193
+ const file = Bun.file(manifestFile)
194
+ if (await file.exists()) {
195
+ Bun.spawn([ENGRAM_BIN, "sync", "--import"], {
196
+ cwd: ctx.directory,
197
+ stdout: "ignore",
198
+ stderr: "ignore",
199
+ stdin: "ignore",
200
+ })
201
+ }
202
+ } catch {
203
+ // Manifest doesn't exist or binary not found — silently skip
204
+ }
205
+
206
+ return {
207
+ // ─── Event Listeners ───────────────────────────────────────────
208
+
209
+ event: async ({ event }) => {
210
+ // --- Session Created ---
211
+ if (event.type === "session.created") {
212
+ // Bug fix (#116): session data is nested under event.properties.info,
213
+ // not event.properties directly.
214
+ const info = (event.properties as any)?.info
215
+ const sessionId = info?.id
216
+ const parentID = info?.parentID
217
+ const title: string = info?.title ?? ""
218
+
219
+ // Sub-agent sessions (created via Task()) must NOT be registered as
220
+ // top-level Engram sessions. They cause massive session inflation
221
+ // (e.g. 170 sessions for 1 real conversation).
222
+ //
223
+ // Detection heuristics:
224
+ // - parentID is set on all Task() sub-agent sessions
225
+ // - title ends with " subagent)" as a secondary signal
226
+ const isSubAgent = !!parentID || title.endsWith(" subagent)")
227
+
228
+ if (sessionId && !isSubAgent) {
229
+ await ensureSession(sessionId)
230
+ } else if (sessionId && isSubAgent) {
231
+ // Remember this as a sub-agent session so tool-hook calls
232
+ // to ensureSession() are also suppressed for it.
233
+ subAgentSessions.add(sessionId)
234
+ }
235
+ }
236
+
237
+ // --- Session Deleted ---
238
+ if (event.type === "session.deleted") {
239
+ // Same properties.info path as session.created.
240
+ const info = (event.properties as any)?.info
241
+ const sessionId = info?.id
242
+ if (sessionId) {
243
+ toolCounts.delete(sessionId)
244
+ knownSessions.delete(sessionId)
245
+ subAgentSessions.delete(sessionId)
246
+ }
247
+ }
248
+
249
+ },
250
+
251
+ // ─── User Prompt Capture ──────────────────────────────────────
252
+ // chat.message is called once per user message, before the LLM sees it.
253
+ // input.sessionID is always reliable here (no knownSessions workaround).
254
+ // output.message is typed as UserMessage (role:"user" already guaranteed).
255
+ // output.parts contains TextPart[] with the actual message text.
256
+
257
+ "chat.message": async (input, output) => {
258
+ // Skip sub-agent sessions — they inflate session counts (issue #116)
259
+ if (subAgentSessions.has(input.sessionID)) return
260
+
261
+ const sessionId = input.sessionID
262
+
263
+ // Extract text from parts (type:"text")
264
+ const content = output.parts
265
+ .filter((p) => p.type === "text")
266
+ .map((p) => (p as any).text ?? "")
267
+ .join("\n")
268
+ .trim()
269
+
270
+ // Also fallback to summary if parts yield nothing
271
+ const fallback = !content && output.message.summary
272
+ ? `${output.message.summary.title ?? ""}\n${output.message.summary.body ?? ""}`.trim()
273
+ : ""
274
+
275
+ const finalContent = content || fallback
276
+
277
+ // Only capture non-trivial prompts (>10 chars)
278
+ if (finalContent.length > 10) {
279
+ await ensureSession(sessionId)
280
+ await engramFetch("/prompts", {
281
+ method: "POST",
282
+ body: {
283
+ session_id: sessionId,
284
+ content: stripPrivateTags(truncate(finalContent, 2000)),
285
+ project,
286
+ },
287
+ })
288
+ }
289
+ },
290
+
291
+ // ─── Tool Execution Hook ─────────────────────────────────────
292
+ // Count tool calls per session (for session end stats).
293
+ // Also ensures the session exists — handles plugin reload / reconnect.
294
+ // Passive capture: when a Task tool completes, POST its output to
295
+ // the passive capture endpoint so the server extracts learnings.
296
+
297
+ "tool.execute.after": async (input, output) => {
298
+ if (ENGRAM_TOOLS.has(input.tool.toLowerCase())) return
299
+
300
+ // input.sessionID comes from OpenCode — always available
301
+ const sessionId = input.sessionID
302
+ if (sessionId) {
303
+ await ensureSession(sessionId)
304
+ toolCounts.set(sessionId, (toolCounts.get(sessionId) ?? 0) + 1)
305
+ }
306
+
307
+ // Passive capture: extract learnings from Task tool output
308
+ // (OpenCode reports the tool name in lowercase: "task")
309
+ if (input.tool.toLowerCase() === "task" && output && sessionId) {
310
+ const text = typeof output === "string" ? output : JSON.stringify(output)
311
+ if (text.length > 50) {
312
+ await engramFetch("/observations/passive", {
313
+ method: "POST",
314
+ body: {
315
+ session_id: sessionId,
316
+ content: stripPrivateTags(text),
317
+ project,
318
+ source: "task-complete",
319
+ },
320
+ })
321
+ }
322
+ }
323
+ },
324
+
325
+ // ─── System Prompt: Always-on memory instructions ──────────
326
+ // Injects MEMORY_INSTRUCTIONS into the system prompt of every message.
327
+ // This ensures the agent ALWAYS knows about Engram, even after compaction.
328
+ //
329
+ // We append to the last existing system entry instead of pushing a new one.
330
+ // Some models (Qwen3.5, Mistral/Ministral via llama.cpp) reject multiple
331
+ // system messages — their Jinja chat templates only allow a single system
332
+ // block at the beginning. By concatenating, we avoid adding extra system
333
+ // messages that would break these models. See: GitHub issue #23.
334
+
335
+ "experimental.chat.system.transform": async (_input, output) => {
336
+ if (output.system.length > 0) {
337
+ output.system[output.system.length - 1] += "\n\n" + MEMORY_INSTRUCTIONS
338
+ } else {
339
+ output.system.push(MEMORY_INSTRUCTIONS)
340
+ }
341
+ },
342
+
343
+ // ─── Compaction Hook: Persist memory + inject context ──────────
344
+ // Compaction is triggered by the system (not the agent) when context
345
+ // gets too long. The old agent "dies" and a new one starts with the
346
+ // compacted summary. This is our chance to:
347
+ // 1. Auto-save a session checkpoint (the agent can't do this itself)
348
+ // 2. Inject context from previous sessions into the compaction prompt
349
+ // 3. Tell the compressor to remind the new agent to save memories
350
+
351
+ "experimental.session.compacting": async (input, output) => {
352
+ if (input.sessionID) {
353
+ await ensureSession(input.sessionID)
354
+ }
355
+
356
+ // Inject context from previous sessions
357
+ const data = await engramFetch(
358
+ `/context?project=${encodeURIComponent(project)}`
359
+ )
360
+ if (data?.context) {
361
+ output.context.push(data.context)
362
+ }
363
+
364
+ // Tell the compressor to instruct the new agent to persist the
365
+ // compacted summary to Engram. The new agent reads the compacted
366
+ // summary and this instruction, then saves it as a session summary.
367
+ output.context.push(
368
+ `CRITICAL INSTRUCTION FOR COMPACTED SUMMARY:\n` +
369
+ `The agent has access to Engram persistent memory via MCP tools.\n` +
370
+ `You MUST include the following instruction at the TOP of the compacted summary:\n\n` +
371
+ `"FIRST ACTION REQUIRED: Call mem_session_summary with the content of this compacted summary. ` +
372
+ `Use project '${project}' if the tool accepts it. This preserves what was accomplished before compaction. ` +
373
+ `Do this BEFORE any other work. After that, delegate to the global engram subagent to recover only the relevant previous context."\n\n` +
374
+ `This is NOT optional. Without this, everything done before compaction is lost from memory.`
375
+ )
376
+ },
377
+ }
378
+ }