gsd-pi 2.75.0-dev.b6ad8c5f7 → 2.75.0-dev.e41b70b10

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 (181) hide show
  1. package/dist/resources/extensions/gsd/auto/phases.js +2 -0
  2. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
  3. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
  6. package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
  7. package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
  8. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +128 -0
  9. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  10. package/dist/resources/extensions/gsd/bootstrap/system-context.js +17 -4
  11. package/dist/resources/extensions/gsd/commands/handlers/onboarding.js +52 -92
  12. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  13. package/dist/resources/extensions/gsd/commands-memory.js +462 -0
  14. package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
  15. package/dist/resources/extensions/gsd/gsd-db.js +237 -4
  16. package/dist/resources/extensions/gsd/memory-embeddings.js +219 -0
  17. package/dist/resources/extensions/gsd/memory-extractor.js +78 -27
  18. package/dist/resources/extensions/gsd/memory-ingest.js +218 -0
  19. package/dist/resources/extensions/gsd/memory-relations.js +189 -0
  20. package/dist/resources/extensions/gsd/memory-source-store.js +113 -0
  21. package/dist/resources/extensions/gsd/memory-store.js +299 -6
  22. package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
  23. package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
  24. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  25. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  26. package/dist/resources/extensions/gsd/tools/memory-tools.js +306 -0
  27. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
  28. package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
  29. package/dist/resources/extensions/search-the-web/native-search.js +13 -2
  30. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  31. package/dist/web/standalone/.next/BUILD_ID +1 -1
  32. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  33. package/dist/web/standalone/.next/build-manifest.json +2 -2
  34. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  35. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.html +1 -1
  52. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  59. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  61. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  62. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  63. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  64. package/package.json +1 -1
  65. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  66. package/packages/mcp-server/dist/server.js +12 -10
  67. package/packages/mcp-server/dist/server.js.map +1 -1
  68. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  69. package/packages/mcp-server/dist/session-manager.js +8 -1
  70. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  71. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
  72. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  73. package/packages/mcp-server/dist/workflow-tools.js +207 -71
  74. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  75. package/packages/mcp-server/src/mcp-server.test.ts +40 -4
  76. package/packages/mcp-server/src/server.ts +12 -10
  77. package/packages/mcp-server/src/session-manager.ts +10 -3
  78. package/packages/mcp-server/src/workflow-tools.test.ts +346 -1
  79. package/packages/mcp-server/src/workflow-tools.ts +228 -75
  80. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  81. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  82. package/packages/pi-ai/dist/index.d.ts +1 -0
  83. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  84. package/packages/pi-ai/dist/index.js +1 -0
  85. package/packages/pi-ai/dist/index.js.map +1 -1
  86. package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
  87. package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
  88. package/packages/pi-ai/dist/providers/api-family.js +47 -0
  89. package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
  90. package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
  91. package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
  92. package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
  93. package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
  94. package/packages/pi-ai/src/index.ts +1 -0
  95. package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
  96. package/packages/pi-ai/src/providers/api-family.ts +57 -0
  97. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  98. package/packages/pi-coding-agent/dist/cli/args.d.ts +6 -0
  99. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/cli/args.js +14 -4
  101. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/cli/args.test.d.ts +2 -0
  103. package/packages/pi-coding-agent/dist/cli/args.test.d.ts.map +1 -0
  104. package/packages/pi-coding-agent/dist/cli/args.test.js +38 -0
  105. package/packages/pi-coding-agent/dist/cli/args.test.js.map +1 -0
  106. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
  107. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  110. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
  114. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -0
  116. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/sdk.js +7 -1
  118. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/main.js +3 -0
  121. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  122. package/packages/pi-coding-agent/src/cli/args.test.ts +44 -0
  123. package/packages/pi-coding-agent/src/cli/args.ts +21 -6
  124. package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
  125. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
  126. package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
  127. package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
  128. package/packages/pi-coding-agent/src/main.ts +4 -0
  129. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  130. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
  131. package/src/resources/extensions/gsd/auto/phases.ts +3 -0
  132. package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
  133. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
  134. package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
  135. package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
  136. package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
  137. package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
  138. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +158 -0
  139. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  140. package/src/resources/extensions/gsd/bootstrap/system-context.ts +20 -4
  141. package/src/resources/extensions/gsd/commands/handlers/onboarding.ts +65 -131
  142. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  143. package/src/resources/extensions/gsd/commands-memory.ts +551 -0
  144. package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
  145. package/src/resources/extensions/gsd/gsd-db.ts +273 -4
  146. package/src/resources/extensions/gsd/memory-embeddings.ts +235 -0
  147. package/src/resources/extensions/gsd/memory-extractor.ts +100 -34
  148. package/src/resources/extensions/gsd/memory-ingest.ts +286 -0
  149. package/src/resources/extensions/gsd/memory-relations.ts +240 -0
  150. package/src/resources/extensions/gsd/memory-source-store.ts +138 -0
  151. package/src/resources/extensions/gsd/memory-store.ts +351 -7
  152. package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
  153. package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
  154. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  155. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  156. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
  157. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  158. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  159. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -2
  160. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  161. package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
  162. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  163. package/src/resources/extensions/gsd/tests/memory-embeddings.test.ts +213 -0
  164. package/src/resources/extensions/gsd/tests/memory-ingest.test.ts +153 -0
  165. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +107 -0
  166. package/src/resources/extensions/gsd/tests/memory-relations.test.ts +175 -0
  167. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  168. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +295 -0
  169. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
  170. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
  171. package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
  172. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
  173. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
  174. package/src/resources/extensions/gsd/tools/memory-tools.ts +380 -0
  175. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
  176. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  177. package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
  178. package/src/resources/extensions/search-the-web/native-search.ts +13 -3
  179. package/src/resources/extensions/gsd/tests/onboarding-handler-loader.test.ts +0 -41
  180. /package/dist/web/standalone/.next/static/{J2z3GMC9QtSLr7gyoM38c → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
  181. /package/dist/web/standalone/.next/static/{J2z3GMC9QtSLr7gyoM38c → By_yegSJ-AA1OP0QjYbSl}/_ssgManifest.js +0 -0
@@ -16,7 +16,7 @@ import type {
16
16
  VerificationContext,
17
17
  VerificationResult,
18
18
  } from "../auto-verification.js";
19
- import type { DispatchAction } from "../auto-dispatch.js";
19
+ import type { DispatchAction, DispatchContext } from "../auto-dispatch.js";
20
20
  import type { WorktreeResolver } from "../worktree-resolver.js";
21
21
  import type { CmuxLogLevel } from "../../cmux/index.js";
22
22
  import type { JournalEntry } from "../journal.js";
@@ -145,15 +145,7 @@ export interface LoopDeps {
145
145
  } | null>;
146
146
 
147
147
  // Dispatch
148
- resolveDispatch: (dctx: {
149
- basePath: string;
150
- mid: string;
151
- midTitle: string;
152
- state: GSDState;
153
- prefs: GSDPreferences | undefined;
154
- session?: AutoSession;
155
- structuredQuestionsAvailable?: "true" | "false";
156
- }) => Promise<DispatchAction>;
148
+ resolveDispatch: (dctx: DispatchContext) => Promise<DispatchAction>;
157
149
  runPreDispatchHooks: (
158
150
  unitType: string,
159
151
  unitId: string,
@@ -50,6 +50,7 @@ import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "
50
50
  import { getEligibleSlices } from "../slice-parallel-eligibility.js";
51
51
  import { startSliceParallel } from "../slice-parallel-orchestrator.js";
52
52
  import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
53
+ import type { MinimalModelRegistry } from "../context-budget.js";
53
54
  import { ensurePlanV2Graph } from "../uok/plan-v2.js";
54
55
  import { resolveUokFlags } from "../uok/flags.js";
55
56
  import { UokGateRunner } from "../uok/gate-runner.js";
@@ -857,6 +858,8 @@ export async function runDispatch(
857
858
  prefs,
858
859
  session: s,
859
860
  structuredQuestionsAvailable,
861
+ sessionContextWindow: ctx.model?.contextWindow,
862
+ modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
860
863
  });
861
864
 
862
865
  if (dispatchResult.action === "stop") {
@@ -18,6 +18,7 @@ import { getCurrentBranch } from "./worktree.js";
18
18
  import { getActiveHook } from "./post-unit-hooks.js";
19
19
  import { getLedger, getProjectTotals } from "./metrics.js";
20
20
  import { getErrorMessage } from "./error-utils.js";
21
+ import { nativeIsRepo } from "./native-git-bridge.js";
21
22
  import {
22
23
  resolveMilestoneFile,
23
24
  resolveSliceFile,
@@ -336,6 +337,10 @@ let lastCommitFetchedAt = 0;
336
337
 
337
338
  function refreshLastCommit(basePath: string): void {
338
339
  try {
340
+ if (!nativeIsRepo(basePath)) {
341
+ cachedLastCommit = null;
342
+ return;
343
+ }
339
344
  const raw = execFileSync("git", ["log", "-1", "--format=%cr|%s"], {
340
345
  cwd: basePath,
341
346
  encoding: "utf-8",
@@ -349,10 +354,12 @@ function refreshLastCommit(basePath: string): void {
349
354
  message: raw.slice(sep + 1),
350
355
  };
351
356
  }
352
- lastCommitFetchedAt = Date.now();
353
357
  } catch (err) {
354
358
  // Non-fatal — just skip last commit display
359
+ cachedLastCommit = null;
355
360
  logWarning("dashboard", `operation failed: ${err instanceof Error ? err.message : String(err)}`);
361
+ } finally {
362
+ lastCommitFetchedAt = Date.now();
356
363
  }
357
364
  }
358
365
 
@@ -364,6 +371,23 @@ function getLastCommit(basePath: string): { timeAgo: string; message: string } |
364
371
  return cachedLastCommit;
365
372
  }
366
373
 
374
+ export function _resetLastCommitCacheForTests(): void {
375
+ cachedLastCommit = null;
376
+ lastCommitFetchedAt = 0;
377
+ }
378
+
379
+ export function _refreshLastCommitForTests(basePath: string): void {
380
+ refreshLastCommit(basePath);
381
+ }
382
+
383
+ export function _getLastCommitForTests(basePath: string): { timeAgo: string; message: string } | null {
384
+ return getLastCommit(basePath);
385
+ }
386
+
387
+ export function _getLastCommitFetchedAtForTests(): number {
388
+ return lastCommitFetchedAt;
389
+ }
390
+
367
391
  // ─── Footer Factory ───────────────────────────────────────────────────────────
368
392
 
369
393
  /**
@@ -28,6 +28,7 @@ import {
28
28
  buildReplanSlicePrompt,
29
29
  } from "./auto-prompts.js";
30
30
  import { loadEffectiveGSDPreferences } from "./preferences.js";
31
+ import type { MinimalModelRegistry } from "./context-budget.js";
31
32
  import { pauseAuto } from "./auto.js";
32
33
  import {
33
34
  getWorkflowTransportSupportError,
@@ -104,7 +105,13 @@ export async function dispatchDirectPhase(
104
105
  }
105
106
  unitType = "plan-slice";
106
107
  unitId = `${mid}/${sid}`;
107
- prompt = await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, base);
108
+ prompt = await buildPlanSlicePrompt(
109
+ mid, midTitle, sid, sTitle, base, undefined,
110
+ {
111
+ sessionContextWindow: ctx.model?.contextWindow,
112
+ modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
113
+ },
114
+ );
108
115
  } else {
109
116
  unitType = "plan-milestone";
110
117
  unitId = mid;
@@ -129,7 +136,13 @@ export async function dispatchDirectPhase(
129
136
  }
130
137
  unitType = "execute-task";
131
138
  unitId = `${mid}/${sid}/${tid}`;
132
- prompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base);
139
+ prompt = await buildExecuteTaskPrompt(
140
+ mid, sid, sTitle, tid, tTitle, base,
141
+ {
142
+ sessionContextWindow: ctx.model?.contextWindow,
143
+ modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
144
+ },
145
+ );
133
146
  break;
134
147
  }
135
148
 
@@ -12,6 +12,7 @@
12
12
  import type { GSDState } from "./types.js";
13
13
  import type { GSDPreferences } from "./preferences.js";
14
14
  import type { UatType } from "./files.js";
15
+ import type { MinimalModelRegistry } from "./context-budget.js";
15
16
  import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
16
17
  import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted, getMilestone, updateMilestoneStatus } from "./gsd-db.js";
17
18
  import { isClosedStatus } from "./status-guards.js";
@@ -81,6 +82,10 @@ export interface DispatchContext {
81
82
  prefs: GSDPreferences | undefined;
82
83
  session?: import("./auto/session.js").AutoSession;
83
84
  structuredQuestionsAvailable?: "true" | "false";
85
+ /** Session model context window in tokens, forwarded to the budget engine's prompt builders. */
86
+ sessionContextWindow?: number;
87
+ /** Model registry forwarded to the budget engine so it can look up the configured executor model. */
88
+ modelRegistry?: MinimalModelRegistry;
84
89
  }
85
90
 
86
91
  export interface DispatchRule {
@@ -521,7 +526,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
521
526
  // auto-heal without either adding an explicit `setSliceSketchFlag(..., false)`
522
527
  // call here or doing so inside the plan-slice tool handler.
523
528
  name: "refining → refine-slice",
524
- match: async ({ state, mid, midTitle, basePath, prefs }) => {
529
+ match: async ({ state, mid, midTitle, basePath, prefs, sessionContextWindow, modelRegistry }) => {
525
530
  if (state.phase !== "refining") return null;
526
531
  if (!state.activeSlice) return missingSliceStop(mid, state.phase);
527
532
  const sid = state.activeSlice.id;
@@ -546,7 +551,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
546
551
  unitId: `${mid}/${sid}`,
547
552
  prompt: await buildPlanSlicePrompt(
548
553
  mid, midTitle, sid, sTitle, basePath, undefined,
549
- softScopeHint ? { softScopeHint } : undefined,
554
+ { ...(softScopeHint ? { softScopeHint } : {}), sessionContextWindow, modelRegistry },
550
555
  ),
551
556
  };
552
557
  }
@@ -554,13 +559,16 @@ export const DISPATCH_RULES: DispatchRule[] = [
554
559
  action: "dispatch",
555
560
  unitType: "refine-slice",
556
561
  unitId: `${mid}/${sid}`,
557
- prompt: await buildRefineSlicePrompt(mid, midTitle, sid, sTitle, basePath),
562
+ prompt: await buildRefineSlicePrompt(
563
+ mid, midTitle, sid, sTitle, basePath, undefined,
564
+ { sessionContextWindow, modelRegistry },
565
+ ),
558
566
  };
559
567
  },
560
568
  },
561
569
  {
562
570
  name: "planning → plan-slice",
563
- match: async ({ state, mid, midTitle, basePath }) => {
571
+ match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry }) => {
564
572
  if (state.phase !== "planning") return null;
565
573
  if (!state.activeSlice) return missingSliceStop(mid, state.phase);
566
574
  const sid = state.activeSlice!.id;
@@ -575,6 +583,8 @@ export const DISPATCH_RULES: DispatchRule[] = [
575
583
  sid,
576
584
  sTitle,
577
585
  basePath,
586
+ undefined,
587
+ { sessionContextWindow, modelRegistry },
578
588
  ),
579
589
  };
580
590
  },
@@ -635,7 +645,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
635
645
  },
636
646
  {
637
647
  name: "executing → reactive-execute (parallel dispatch)",
638
- match: async ({ state, mid, midTitle, basePath, prefs }) => {
648
+ match: async ({ state, mid, midTitle, basePath, prefs, sessionContextWindow, modelRegistry }) => {
639
649
  if (state.phase !== "executing" || !state.activeTask) return null;
640
650
  if (!state.activeSlice) return null; // fall through
641
651
 
@@ -726,6 +736,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
726
736
  selected,
727
737
  basePath,
728
738
  subagentModel,
739
+ { sessionContextWindow, modelRegistry },
729
740
  ),
730
741
  };
731
742
  } catch (err) {
@@ -737,7 +748,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
737
748
  },
738
749
  {
739
750
  name: "executing → execute-task (recover missing task plan → plan-slice)",
740
- match: async ({ state, mid, midTitle, basePath }) => {
751
+ match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry }) => {
741
752
  if (state.phase !== "executing" || !state.activeTask) return null;
742
753
  if (!state.activeSlice) return missingSliceStop(mid, state.phase);
743
754
  const sid = state.activeSlice!.id;
@@ -761,6 +772,8 @@ export const DISPATCH_RULES: DispatchRule[] = [
761
772
  sid,
762
773
  sTitle,
763
774
  basePath,
775
+ undefined,
776
+ { sessionContextWindow, modelRegistry },
764
777
  ),
765
778
  };
766
779
  }
@@ -770,7 +783,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
770
783
  },
771
784
  {
772
785
  name: "executing → execute-task",
773
- match: async ({ state, mid, basePath }) => {
786
+ match: async ({ state, mid, basePath, sessionContextWindow, modelRegistry }) => {
774
787
  if (state.phase !== "executing" || !state.activeTask) return null;
775
788
  if (!state.activeSlice) return missingSliceStop(mid, state.phase);
776
789
  const sid = state.activeSlice!.id;
@@ -789,6 +802,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
789
802
  tid,
790
803
  tTitle,
791
804
  basePath,
805
+ { sessionContextWindow, modelRegistry },
792
806
  ),
793
807
  };
794
808
  },
@@ -488,7 +488,9 @@ export function resolveModelId<T extends { id: string; provider: string }>(
488
488
  if (providerMatch) return providerMatch;
489
489
  }
490
490
 
491
- // Prefer "anthropic" as the canonical provider for Anthropic models
491
+ // Prefer "anthropic" as the canonical provider for Anthropic models.
492
+ // Transport-specific tiebreaker (ADR-012): intentionally keys on provider,
493
+ // not api — we want the plain Anthropic transport when multiple are available.
492
494
  const anthropicMatch = candidates.find(m => m.provider === "anthropic");
493
495
  if (anthropicMatch) return anthropicMatch;
494
496
 
@@ -23,7 +23,7 @@ import type { GSDPreferences } from "./preferences.js";
23
23
  import { getLoadedSkills, type Skill } from "@gsd/pi-coding-agent";
24
24
  import { join, basename } from "node:path";
25
25
  import { existsSync } from "node:fs";
26
- import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
26
+ import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary, type MinimalModelRegistry } from "./context-budget.js";
27
27
  import { getPendingGates, getPendingGatesForTurn } from "./gsd-db.js";
28
28
  import {
29
29
  GATE_REGISTRY,
@@ -94,14 +94,19 @@ function capPreamble(preamble: string): string {
94
94
  * Uses the budget engine to compute task count ranges and inline context budgets
95
95
  * based on the configured executor model's context window.
96
96
  */
97
- function formatExecutorConstraints(): string {
97
+ function formatExecutorConstraints(
98
+ sessionContextWindow?: number,
99
+ modelRegistry?: MinimalModelRegistry,
100
+ ): string {
98
101
  let windowTokens: number;
99
102
  try {
100
103
  const prefs = loadEffectiveGSDPreferences();
101
- windowTokens = resolveExecutorContextWindow(undefined, prefs?.preferences);
104
+ windowTokens = resolveExecutorContextWindow(modelRegistry, prefs?.preferences, sessionContextWindow);
102
105
  } catch (e) {
103
106
  logWarning("prompt", `resolveExecutorContextWindow failed: ${(e as Error).message}`);
104
- windowTokens = 200_000; // safe default
107
+ // Delegate to the budget engine without prefs (the path that just threw)
108
+ // so DEFAULT_CONTEXT_WINDOW stays the single source of truth.
109
+ windowTokens = resolveExecutorContextWindow(undefined, undefined, sessionContextWindow);
105
110
  }
106
111
  const budgets = computeBudgets(windowTokens);
107
112
  const { min, max } = budgets.taskCountRange;
@@ -1288,8 +1293,13 @@ async function renderSlicePrompt(options: {
1288
1293
  promptTemplate: "plan-slice" | "refine-slice";
1289
1294
  prependBlocks?: string[];
1290
1295
  extraVars?: Record<string, string>;
1296
+ sessionContextWindow?: number;
1297
+ modelRegistry?: MinimalModelRegistry;
1291
1298
  }): Promise<string> {
1292
- const { mid, sid, sTitle, base, level, promptTemplate, prependBlocks = [], extraVars = {} } = options;
1299
+ const {
1300
+ mid, sid, sTitle, base, level, promptTemplate, prependBlocks = [], extraVars = {},
1301
+ sessionContextWindow, modelRegistry,
1302
+ } = options;
1293
1303
 
1294
1304
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
1295
1305
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
@@ -1341,7 +1351,7 @@ async function renderSlicePrompt(options: {
1341
1351
  if (overridesInline) inlined.unshift(overridesInline);
1342
1352
 
1343
1353
  const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1344
- const executorContextConstraints = formatExecutorConstraints();
1354
+ const executorContextConstraints = formatExecutorConstraints(sessionContextWindow, modelRegistry);
1345
1355
  const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
1346
1356
  const commitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git.";
1347
1357
 
@@ -1370,7 +1380,7 @@ async function renderSlicePrompt(options: {
1370
1380
 
1371
1381
  export async function buildPlanSlicePrompt(
1372
1382
  mid: string, _midTitle: string, sid: string, sTitle: string, base: string, level?: InlineLevel,
1373
- options?: { softScopeHint?: string },
1383
+ options?: { softScopeHint?: string; sessionContextWindow?: number; modelRegistry?: MinimalModelRegistry },
1374
1384
  ): Promise<string> {
1375
1385
  const prependBlocks: string[] = [];
1376
1386
  // ADR-011: when the refining-phase dispatch rule gracefully downgrades to
@@ -1388,6 +1398,8 @@ export async function buildPlanSlicePrompt(
1388
1398
  level: level ?? resolveInlineLevel(),
1389
1399
  promptTemplate: "plan-slice",
1390
1400
  prependBlocks,
1401
+ sessionContextWindow: options?.sessionContextWindow,
1402
+ modelRegistry: options?.modelRegistry,
1391
1403
  });
1392
1404
  }
1393
1405
 
@@ -1400,6 +1412,7 @@ export async function buildPlanSlicePrompt(
1400
1412
  */
1401
1413
  export async function buildRefineSlicePrompt(
1402
1414
  mid: string, _midTitle: string, sid: string, sTitle: string, base: string, level?: InlineLevel,
1415
+ options?: { sessionContextWindow?: number; modelRegistry?: MinimalModelRegistry },
1403
1416
  ): Promise<string> {
1404
1417
  // Pull the stored sketch scope from the DB — the hard constraint we plan within.
1405
1418
  let sketchScope = "";
@@ -1426,6 +1439,8 @@ export async function buildRefineSlicePrompt(
1426
1439
  promptTemplate: "refine-slice",
1427
1440
  prependBlocks,
1428
1441
  extraVars: { sketchScope },
1442
+ sessionContextWindow: options?.sessionContextWindow,
1443
+ modelRegistry: options?.modelRegistry,
1429
1444
  });
1430
1445
  }
1431
1446
 
@@ -1434,6 +1449,10 @@ export interface ExecuteTaskPromptOptions {
1434
1449
  level?: InlineLevel;
1435
1450
  /** Override carry-forward paths (dependency-based instead of order-based). */
1436
1451
  carryForwardPaths?: string[];
1452
+ /** Session model context window in tokens, forwarded to the budget engine. */
1453
+ sessionContextWindow?: number;
1454
+ /** Model registry forwarded to the budget engine for executor-model lookup. */
1455
+ modelRegistry?: MinimalModelRegistry;
1437
1456
  }
1438
1457
 
1439
1458
  export async function buildExecuteTaskPrompt(
@@ -1525,7 +1544,7 @@ export async function buildExecuteTaskPrompt(
1525
1544
 
1526
1545
  // Compute verification budget for the executor's context window (issue #707)
1527
1546
  const prefs = loadEffectiveGSDPreferences();
1528
- const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
1547
+ const contextWindow = resolveExecutorContextWindow(opts.modelRegistry, prefs?.preferences, opts.sessionContextWindow);
1529
1548
  const budgets = computeBudgets(contextWindow);
1530
1549
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
1531
1550
 
@@ -2101,6 +2120,7 @@ export async function buildReactiveExecutePrompt(
2101
2120
  mid: string, midTitle: string, sid: string, sTitle: string,
2102
2121
  readyTaskIds: string[], base: string,
2103
2122
  subagentModel?: string,
2123
+ opts?: { sessionContextWindow?: number; modelRegistry?: MinimalModelRegistry },
2104
2124
  ): Promise<string> {
2105
2125
  const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
2106
2126
 
@@ -2142,7 +2162,11 @@ export async function buildReactiveExecutePrompt(
2142
2162
  // Build a full execute-task prompt with dependency-based carry-forward
2143
2163
  const taskPrompt = await buildExecuteTaskPrompt(
2144
2164
  mid, sid, sTitle, tid, tTitle, base,
2145
- { carryForwardPaths: depPaths },
2165
+ {
2166
+ carryForwardPaths: depPaths,
2167
+ sessionContextWindow: opts?.sessionContextWindow,
2168
+ modelRegistry: opts?.modelRegistry,
2169
+ },
2146
2170
  );
2147
2171
 
2148
2172
  const modelSuffix = subagentModel ? ` with model: "${subagentModel}"` : "";
@@ -238,6 +238,14 @@ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void
238
238
  }
239
239
  }
240
240
 
241
+ function isProjectGsdSymlink(basePath: string): boolean {
242
+ try {
243
+ return lstatSyncFn(join(basePath, ".gsd")).isSymbolicLink();
244
+ } catch {
245
+ return false;
246
+ }
247
+ }
248
+
241
249
  // ─── Build Artifact Auto-Resolve ─────────────────────────────────────────────
242
250
 
243
251
  /** Patterns for machine-generated build artifacts that can be safely
@@ -1675,12 +1683,19 @@ export function mergeMilestoneToMain(
1675
1683
  // CONTEXT files into the stash. If stash pop later fails, those files
1676
1684
  // are permanently trapped in the stash entry and lost on the next
1677
1685
  // stash push or drop.
1686
+ //
1687
+ // When `.gsd` itself is a symlink, Git rejects pathspecs below it
1688
+ // ("beyond a symbolic link"). In that layout, exclude the whole symlink
1689
+ // and keep stashing real project files that could block the merge.
1690
+ const stashPathspecs = isProjectGsdSymlink(originalBasePath_)
1691
+ ? [".", ":(exclude).gsd"]
1692
+ : [":(exclude).gsd/milestones"];
1678
1693
  execFileSync(
1679
1694
  "git",
1680
1695
  [
1681
1696
  "stash", "push", "--include-untracked",
1682
1697
  "-m", `gsd: pre-merge stash for ${milestoneId}`,
1683
- "--", ":(exclude).gsd/milestones",
1698
+ "--", ...stashPathspecs,
1684
1699
  ],
1685
1700
  { cwd: originalBasePath_, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
1686
1701
  );
@@ -0,0 +1,158 @@
1
+ // GSD2 — Memory tool registration
2
+ //
3
+ // Exposes the memory-layer tools (capture_thought, memory_query, gsd_graph)
4
+ // to the LLM over MCP. All three degrade gracefully when the GSD database
5
+ // is unavailable.
6
+
7
+ import { Type } from "@sinclair/typebox";
8
+ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
9
+
10
+ import { ensureDbOpen } from "./dynamic-tools.js";
11
+ import {
12
+ executeGsdGraph,
13
+ executeMemoryCapture,
14
+ executeMemoryQuery,
15
+ } from "../tools/memory-tools.js";
16
+
17
+ export function registerMemoryTools(pi: ExtensionAPI): void {
18
+ // ─── capture_thought ────────────────────────────────────────────────────
19
+
20
+ pi.registerTool({
21
+ name: "capture_thought",
22
+ label: "Capture Thought",
23
+ description:
24
+ "Record a durable piece of project knowledge (decision, convention, gotcha, pattern, " +
25
+ "preference, or environment detail) into the GSD memory store. Use sparingly — one memory " +
26
+ "per genuinely reusable insight, not per task.",
27
+ promptSnippet:
28
+ "Capture a durable project insight into the GSD memory store (categories: architecture, convention, gotcha, pattern, preference, environment)",
29
+ promptGuidelines: [
30
+ "Use capture_thought for insights that will remain useful across future sessions.",
31
+ "Do NOT capture one-off bug fixes, temporary state, secrets, or task-specific details.",
32
+ "Keep content to 1–3 sentences.",
33
+ "Set confidence: 0.6 tentative, 0.8 solid, 0.95 well-confirmed (default 0.8).",
34
+ ],
35
+ parameters: Type.Object({
36
+ category: Type.Union(
37
+ [
38
+ Type.Literal("architecture"),
39
+ Type.Literal("convention"),
40
+ Type.Literal("gotcha"),
41
+ Type.Literal("preference"),
42
+ Type.Literal("environment"),
43
+ Type.Literal("pattern"),
44
+ ],
45
+ { description: "Memory category" },
46
+ ),
47
+ content: Type.String({ description: "The memory text (1–3 sentences, no secrets)" }),
48
+ confidence: Type.Optional(
49
+ Type.Number({ description: "0.1–0.99, default 0.8", minimum: 0.1, maximum: 0.99 }),
50
+ ),
51
+ tags: Type.Optional(Type.Array(Type.String(), { description: "Free-form tags (reserved for future use)" })),
52
+ scope: Type.Optional(Type.String({ description: "Scope name (reserved for future use; defaults to project)" })),
53
+ }),
54
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
55
+ const ok = await ensureDbOpen();
56
+ if (!ok) {
57
+ return {
58
+ content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot capture memory." }],
59
+ details: { operation: "memory_capture", error: "db_unavailable" },
60
+ isError: true,
61
+ };
62
+ }
63
+ return executeMemoryCapture(params as Parameters<typeof executeMemoryCapture>[0]);
64
+ },
65
+ });
66
+
67
+ // ─── memory_query ───────────────────────────────────────────────────────
68
+
69
+ pi.registerTool({
70
+ name: "memory_query",
71
+ label: "Query Memory",
72
+ description:
73
+ "Search the GSD memory store for relevant memories. Phase 1 uses keyword matching ranked " +
74
+ "by confidence and reinforcement; future phases add semantic (embedding) retrieval.",
75
+ promptSnippet:
76
+ "Search the GSD memory store by keyword; returns ranked memories with id, category, and content",
77
+ promptGuidelines: [
78
+ "Use memory_query when you need durable project context that may not be in the current prompt.",
79
+ "Provide a short keyword-style query — not a full question.",
80
+ "Use category to narrow results to gotchas, conventions, architecture notes, etc.",
81
+ ],
82
+ parameters: Type.Object({
83
+ query: Type.String({ description: "Keyword query (2+ char terms)" }),
84
+ k: Type.Optional(Type.Number({ description: "Max results (default 10, max 50)", minimum: 1, maximum: 50 })),
85
+ category: Type.Optional(
86
+ Type.Union(
87
+ [
88
+ Type.Literal("architecture"),
89
+ Type.Literal("convention"),
90
+ Type.Literal("gotcha"),
91
+ Type.Literal("preference"),
92
+ Type.Literal("environment"),
93
+ Type.Literal("pattern"),
94
+ ],
95
+ { description: "Restrict results to a single category" },
96
+ ),
97
+ ),
98
+ scope: Type.Optional(Type.String({ description: "Only include memories with this scope (e.g. 'project', 'global')" })),
99
+ tag: Type.Optional(Type.String({ description: "Only include memories tagged with this value" })),
100
+ include_superseded: Type.Optional(Type.Boolean({ description: "Include superseded memories (default false)" })),
101
+ reinforce_hits: Type.Optional(
102
+ Type.Boolean({ description: "Increment hit_count on returned memories (default false)" }),
103
+ ),
104
+ }),
105
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
106
+ const ok = await ensureDbOpen();
107
+ if (!ok) {
108
+ return {
109
+ content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot query memory." }],
110
+ details: { operation: "memory_query", error: "db_unavailable" },
111
+ isError: true,
112
+ };
113
+ }
114
+ return executeMemoryQuery(params as Parameters<typeof executeMemoryQuery>[0]);
115
+ },
116
+ });
117
+
118
+ // ─── gsd_graph ──────────────────────────────────────────────────────────
119
+
120
+ pi.registerTool({
121
+ name: "gsd_graph",
122
+ label: "GSD Knowledge Graph",
123
+ description:
124
+ "Inspect the relationship graph between memories. mode=query walks supersedes edges from a " +
125
+ "given memoryId; mode=build is a placeholder that future phases will use to rebuild graph " +
126
+ "edges from milestone LEARNINGS artifacts.",
127
+ promptSnippet: "Query the memory relationship graph or trigger a rebuild",
128
+ promptGuidelines: [
129
+ "Use mode=query with a memoryId when you want to see how a memory relates to others.",
130
+ "Phase 1 only exposes supersedes edges; additional relation types arrive in later phases.",
131
+ ],
132
+ parameters: Type.Object({
133
+ mode: Type.Union([Type.Literal("build"), Type.Literal("query")], {
134
+ description: "build = recompute graph (placeholder), query = inspect edges",
135
+ }),
136
+ memoryId: Type.Optional(Type.String({ description: "Memory ID (required when mode=query)" })),
137
+ depth: Type.Optional(Type.Number({ description: "Hops to traverse (0–5, default 1)", minimum: 0, maximum: 5 })),
138
+ rel: Type.Optional(Type.Union([
139
+ Type.Literal("related_to"),
140
+ Type.Literal("depends_on"),
141
+ Type.Literal("contradicts"),
142
+ Type.Literal("elaborates"),
143
+ Type.Literal("supersedes"),
144
+ ], { description: "Only include edges with this relation type" })),
145
+ }),
146
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
147
+ const ok = await ensureDbOpen();
148
+ if (!ok) {
149
+ return {
150
+ content: [{ type: "text" as const, text: "Error: GSD database is not available." }],
151
+ details: { operation: "gsd_graph", error: "db_unavailable" },
152
+ isError: true,
153
+ };
154
+ }
155
+ return executeGsdGraph(params as Parameters<typeof executeGsdGraph>[0]);
156
+ },
157
+ });
158
+ }
@@ -9,6 +9,7 @@ import { loadEcosystemExtensions } from "../ecosystem/loader.js";
9
9
  import { registerDbTools } from "./db-tools.js";
10
10
  import { registerDynamicTools } from "./dynamic-tools.js";
11
11
  import { registerJournalTools } from "./journal-tools.js";
12
+ import { registerMemoryTools } from "./memory-tools.js";
12
13
  import { registerQueryTools } from "./query-tools.js";
13
14
  import { registerHooks } from "./register-hooks.js";
14
15
  import { registerShortcuts } from "./register-shortcuts.js";
@@ -85,6 +86,7 @@ export function registerGsdExtension(pi: ExtensionAPI): void {
85
86
  ["db-tools", () => registerDbTools(pi)],
86
87
  ["journal-tools", () => registerJournalTools(pi)],
87
88
  ["query-tools", () => registerQueryTools(pi)],
89
+ ["memory-tools", () => registerMemoryTools(pi)],
88
90
  ["shortcuts", () => registerShortcuts(pi)],
89
91
  ["hooks", () => registerHooks(pi, ecosystemHandlers)],
90
92
  ["ecosystem", () => {
@@ -113,10 +113,26 @@ export async function buildBeforeAgentStartResult(
113
113
 
114
114
  let memoryBlock = "";
115
115
  try {
116
- const { formatMemoriesForPrompt, getActiveMemoriesRanked } = await import("../memory-store.js");
117
- const memories = getActiveMemoriesRanked(30);
118
- if (memories.length > 0) {
119
- const formatted = formatMemoriesForPrompt(memories, 2000);
116
+ const { formatMemoriesForPrompt, getActiveMemoriesRanked, queryMemoriesRanked } = await import("../memory-store.js");
117
+
118
+ // Always-on "critical" set — small, stable memories that belong in every
119
+ // turn (gotchas, environment, conventions). Ranked by the existing score.
120
+ const CRITICAL_CATEGORIES = new Set(["gotcha", "environment", "convention"]);
121
+ const allRanked = getActiveMemoriesRanked(60);
122
+ const critical = allRanked.filter((m) => CRITICAL_CATEGORIES.has(m.category)).slice(0, 5);
123
+ const criticalIds = new Set(critical.map((m) => m.id));
124
+
125
+ // Prompt-relevance set — hybrid FTS5 + (future) semantic retrieval.
126
+ let relevant: typeof allRanked = [];
127
+ const userPrompt = (event.prompt ?? "").trim();
128
+ if (userPrompt) {
129
+ const hits = queryMemoriesRanked({ query: userPrompt, k: 10 });
130
+ relevant = hits.map((h) => h.memory).filter((m) => !criticalIds.has(m.id));
131
+ }
132
+
133
+ const merged = [...critical, ...relevant];
134
+ if (merged.length > 0) {
135
+ const formatted = formatMemoriesForPrompt(merged, 2000);
120
136
  if (formatted) {
121
137
  memoryBlock = `\n\n${formatted}`;
122
138
  }