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
@@ -607,6 +607,8 @@ export async function runDispatch(ic, preData, loopState) {
607
607
  prefs,
608
608
  session: s,
609
609
  structuredQuestionsAvailable,
610
+ sessionContextWindow: ctx.model?.contextWindow,
611
+ modelRegistry: ctx.modelRegistry,
610
612
  });
611
613
  if (dispatchResult.action === "stop") {
612
614
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "dispatch-stop", rule: dispatchResult.matchedRule, data: { reason: dispatchResult.reason } });
@@ -9,6 +9,7 @@ import { getCurrentBranch } from "./worktree.js";
9
9
  import { getActiveHook } from "./post-unit-hooks.js";
10
10
  import { getLedger, getProjectTotals } from "./metrics.js";
11
11
  import { getErrorMessage } from "./error-utils.js";
12
+ import { nativeIsRepo } from "./native-git-bridge.js";
12
13
  import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
13
14
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
14
15
  import { execFileSync } from "node:child_process";
@@ -256,6 +257,10 @@ let cachedLastCommit = null;
256
257
  let lastCommitFetchedAt = 0;
257
258
  function refreshLastCommit(basePath) {
258
259
  try {
260
+ if (!nativeIsRepo(basePath)) {
261
+ cachedLastCommit = null;
262
+ return;
263
+ }
259
264
  const raw = execFileSync("git", ["log", "-1", "--format=%cr|%s"], {
260
265
  cwd: basePath,
261
266
  encoding: "utf-8",
@@ -269,12 +274,15 @@ function refreshLastCommit(basePath) {
269
274
  message: raw.slice(sep + 1),
270
275
  };
271
276
  }
272
- lastCommitFetchedAt = Date.now();
273
277
  }
274
278
  catch (err) {
275
279
  // Non-fatal — just skip last commit display
280
+ cachedLastCommit = null;
276
281
  logWarning("dashboard", `operation failed: ${err instanceof Error ? err.message : String(err)}`);
277
282
  }
283
+ finally {
284
+ lastCommitFetchedAt = Date.now();
285
+ }
278
286
  }
279
287
  function getLastCommit(basePath) {
280
288
  // Refresh at most every 15 seconds
@@ -283,6 +291,19 @@ function getLastCommit(basePath) {
283
291
  }
284
292
  return cachedLastCommit;
285
293
  }
294
+ export function _resetLastCommitCacheForTests() {
295
+ cachedLastCommit = null;
296
+ lastCommitFetchedAt = 0;
297
+ }
298
+ export function _refreshLastCommitForTests(basePath) {
299
+ refreshLastCommit(basePath);
300
+ }
301
+ export function _getLastCommitForTests(basePath) {
302
+ return getLastCommit(basePath);
303
+ }
304
+ export function _getLastCommitFetchedAtForTests() {
305
+ return lastCommitFetchedAt;
306
+ }
286
307
  // ─── Footer Factory ───────────────────────────────────────────────────────────
287
308
  /**
288
309
  * Footer factory used by auto-mode.
@@ -68,7 +68,10 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
68
68
  }
69
69
  unitType = "plan-slice";
70
70
  unitId = `${mid}/${sid}`;
71
- prompt = await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, base);
71
+ prompt = await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, base, undefined, {
72
+ sessionContextWindow: ctx.model?.contextWindow,
73
+ modelRegistry: ctx.modelRegistry,
74
+ });
72
75
  }
73
76
  else {
74
77
  unitType = "plan-milestone";
@@ -93,7 +96,10 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
93
96
  }
94
97
  unitType = "execute-task";
95
98
  unitId = `${mid}/${sid}/${tid}`;
96
- prompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base);
99
+ prompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, {
100
+ sessionContextWindow: ctx.model?.contextWindow,
101
+ modelRegistry: ctx.modelRegistry,
102
+ });
97
103
  break;
98
104
  }
99
105
  case "complete":
@@ -412,7 +412,7 @@ export const DISPATCH_RULES = [
412
412
  // auto-heal without either adding an explicit `setSliceSketchFlag(..., false)`
413
413
  // call here or doing so inside the plan-slice tool handler.
414
414
  name: "refining → refine-slice",
415
- match: async ({ state, mid, midTitle, basePath, prefs }) => {
415
+ match: async ({ state, mid, midTitle, basePath, prefs, sessionContextWindow, modelRegistry }) => {
416
416
  if (state.phase !== "refining")
417
417
  return null;
418
418
  if (!state.activeSlice)
@@ -438,20 +438,20 @@ export const DISPATCH_RULES = [
438
438
  action: "dispatch",
439
439
  unitType: "plan-slice",
440
440
  unitId: `${mid}/${sid}`,
441
- prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, softScopeHint ? { softScopeHint } : undefined),
441
+ prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { ...(softScopeHint ? { softScopeHint } : {}), sessionContextWindow, modelRegistry }),
442
442
  };
443
443
  }
444
444
  return {
445
445
  action: "dispatch",
446
446
  unitType: "refine-slice",
447
447
  unitId: `${mid}/${sid}`,
448
- prompt: await buildRefineSlicePrompt(mid, midTitle, sid, sTitle, basePath),
448
+ prompt: await buildRefineSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry }),
449
449
  };
450
450
  },
451
451
  },
452
452
  {
453
453
  name: "planning → plan-slice",
454
- match: async ({ state, mid, midTitle, basePath }) => {
454
+ match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry }) => {
455
455
  if (state.phase !== "planning")
456
456
  return null;
457
457
  if (!state.activeSlice)
@@ -462,7 +462,7 @@ export const DISPATCH_RULES = [
462
462
  action: "dispatch",
463
463
  unitType: "plan-slice",
464
464
  unitId: `${mid}/${sid}`,
465
- prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath),
465
+ prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry }),
466
466
  };
467
467
  },
468
468
  },
@@ -511,7 +511,7 @@ export const DISPATCH_RULES = [
511
511
  },
512
512
  {
513
513
  name: "executing → reactive-execute (parallel dispatch)",
514
- match: async ({ state, mid, midTitle, basePath, prefs }) => {
514
+ match: async ({ state, mid, midTitle, basePath, prefs, sessionContextWindow, modelRegistry }) => {
515
515
  if (state.phase !== "executing" || !state.activeTask)
516
516
  return null;
517
517
  if (!state.activeSlice)
@@ -574,7 +574,7 @@ export const DISPATCH_RULES = [
574
574
  action: "dispatch",
575
575
  unitType: "reactive-execute",
576
576
  unitId: `${mid}/${sid}/reactive+${batchSuffix}`,
577
- prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath, subagentModel),
577
+ prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath, subagentModel, { sessionContextWindow, modelRegistry }),
578
578
  };
579
579
  }
580
580
  catch (err) {
@@ -586,7 +586,7 @@ export const DISPATCH_RULES = [
586
586
  },
587
587
  {
588
588
  name: "executing → execute-task (recover missing task plan → plan-slice)",
589
- match: async ({ state, mid, midTitle, basePath }) => {
589
+ match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry }) => {
590
590
  if (state.phase !== "executing" || !state.activeTask)
591
591
  return null;
592
592
  if (!state.activeSlice)
@@ -605,7 +605,7 @@ export const DISPATCH_RULES = [
605
605
  action: "dispatch",
606
606
  unitType: "plan-slice",
607
607
  unitId: `${mid}/${sid}`,
608
- prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath),
608
+ prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry }),
609
609
  };
610
610
  }
611
611
  return null;
@@ -613,7 +613,7 @@ export const DISPATCH_RULES = [
613
613
  },
614
614
  {
615
615
  name: "executing → execute-task",
616
- match: async ({ state, mid, basePath }) => {
616
+ match: async ({ state, mid, basePath, sessionContextWindow, modelRegistry }) => {
617
617
  if (state.phase !== "executing" || !state.activeTask)
618
618
  return null;
619
619
  if (!state.activeSlice)
@@ -626,7 +626,7 @@ export const DISPATCH_RULES = [
626
626
  action: "dispatch",
627
627
  unitType: "execute-task",
628
628
  unitId: `${mid}/${sid}/${tid}`,
629
- prompt: await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, basePath),
629
+ prompt: await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, basePath, { sessionContextWindow, modelRegistry }),
630
630
  };
631
631
  },
632
632
  },
@@ -382,7 +382,9 @@ export function resolveModelId(modelId, availableModels, currentProvider) {
382
382
  if (providerMatch)
383
383
  return providerMatch;
384
384
  }
385
- // Prefer "anthropic" as the canonical provider for Anthropic models
385
+ // Prefer "anthropic" as the canonical provider for Anthropic models.
386
+ // Transport-specific tiebreaker (ADR-012): intentionally keys on provider,
387
+ // not api — we want the plain Anthropic transport when multiple are available.
386
388
  const anthropicMatch = candidates.find(m => m.provider === "anthropic");
387
389
  if (anthropicMatch)
388
390
  return anthropicMatch;
@@ -75,15 +75,17 @@ function capPreamble(preamble) {
75
75
  * Uses the budget engine to compute task count ranges and inline context budgets
76
76
  * based on the configured executor model's context window.
77
77
  */
78
- function formatExecutorConstraints() {
78
+ function formatExecutorConstraints(sessionContextWindow, modelRegistry) {
79
79
  let windowTokens;
80
80
  try {
81
81
  const prefs = loadEffectiveGSDPreferences();
82
- windowTokens = resolveExecutorContextWindow(undefined, prefs?.preferences);
82
+ windowTokens = resolveExecutorContextWindow(modelRegistry, prefs?.preferences, sessionContextWindow);
83
83
  }
84
84
  catch (e) {
85
85
  logWarning("prompt", `resolveExecutorContextWindow failed: ${e.message}`);
86
- windowTokens = 200_000; // safe default
86
+ // Delegate to the budget engine without prefs (the path that just threw)
87
+ // so DEFAULT_CONTEXT_WINDOW stays the single source of truth.
88
+ windowTokens = resolveExecutorContextWindow(undefined, undefined, sessionContextWindow);
87
89
  }
88
90
  const budgets = computeBudgets(windowTokens);
89
91
  const { min, max } = budgets.taskCountRange;
@@ -1136,7 +1138,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
1136
1138
  * sketch-scope constraint).
1137
1139
  */
1138
1140
  async function renderSlicePrompt(options) {
1139
- const { mid, sid, sTitle, base, level, promptTemplate, prependBlocks = [], extraVars = {} } = options;
1141
+ const { mid, sid, sTitle, base, level, promptTemplate, prependBlocks = [], extraVars = {}, sessionContextWindow, modelRegistry, } = options;
1140
1142
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
1141
1143
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1142
1144
  const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH");
@@ -1186,7 +1188,7 @@ async function renderSlicePrompt(options) {
1186
1188
  if (overridesInline)
1187
1189
  inlined.unshift(overridesInline);
1188
1190
  const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1189
- const executorContextConstraints = formatExecutorConstraints();
1191
+ const executorContextConstraints = formatExecutorConstraints(sessionContextWindow, modelRegistry);
1190
1192
  const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
1191
1193
  const commitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git.";
1192
1194
  return loadPrompt(promptTemplate, {
@@ -1226,6 +1228,8 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
1226
1228
  level: level ?? resolveInlineLevel(),
1227
1229
  promptTemplate: "plan-slice",
1228
1230
  prependBlocks,
1231
+ sessionContextWindow: options?.sessionContextWindow,
1232
+ modelRegistry: options?.modelRegistry,
1229
1233
  });
1230
1234
  }
1231
1235
  /**
@@ -1235,7 +1239,7 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
1235
1239
  * blank-sheet planning pass. Reuses inlineDependencySummaries for prior
1236
1240
  * slice SUMMARY and inlines the stored sketch_scope as a hard constraint.
1237
1241
  */
1238
- export async function buildRefineSlicePrompt(mid, _midTitle, sid, sTitle, base, level) {
1242
+ export async function buildRefineSlicePrompt(mid, _midTitle, sid, sTitle, base, level, options) {
1239
1243
  // Pull the stored sketch scope from the DB — the hard constraint we plan within.
1240
1244
  let sketchScope = "";
1241
1245
  try {
@@ -1258,6 +1262,8 @@ export async function buildRefineSlicePrompt(mid, _midTitle, sid, sTitle, base,
1258
1262
  promptTemplate: "refine-slice",
1259
1263
  prependBlocks,
1260
1264
  extraVars: { sketchScope },
1265
+ sessionContextWindow: options?.sessionContextWindow,
1266
+ modelRegistry: options?.modelRegistry,
1261
1267
  });
1262
1268
  }
1263
1269
  export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, level) {
@@ -1323,7 +1329,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
1323
1329
  const overridesSection = formatOverridesSection(activeOverrides);
1324
1330
  // Compute verification budget for the executor's context window (issue #707)
1325
1331
  const prefs = loadEffectiveGSDPreferences();
1326
- const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
1332
+ const contextWindow = resolveExecutorContextWindow(opts.modelRegistry, prefs?.preferences, opts.sessionContextWindow);
1327
1333
  const budgets = computeBudgets(contextWindow);
1328
1334
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
1329
1335
  // Truncate carry-forward section when it exceeds 40% of inline context budget.
@@ -1858,7 +1864,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
1858
1864
  });
1859
1865
  }
1860
1866
  // ─── Reactive Execute Prompt ──────────────────────────────────────────────
1861
- export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base, subagentModel) {
1867
+ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base, subagentModel, opts) {
1862
1868
  const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
1863
1869
  // Build graph for context
1864
1870
  const taskIO = await loadSliceTaskIO(base, mid, sid);
@@ -1889,7 +1895,11 @@ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, rea
1889
1895
  // Build dependency-scoped carry-forward paths for this task
1890
1896
  const depPaths = await getDependencyTaskSummaryPaths(mid, sid, tid, node?.dependsOn ?? [], base);
1891
1897
  // Build a full execute-task prompt with dependency-based carry-forward
1892
- const taskPrompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, { carryForwardPaths: depPaths });
1898
+ const taskPrompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, {
1899
+ carryForwardPaths: depPaths,
1900
+ sessionContextWindow: opts?.sessionContextWindow,
1901
+ modelRegistry: opts?.modelRegistry,
1902
+ });
1893
1903
  const modelSuffix = subagentModel ? ` with model: "${subagentModel}"` : "";
1894
1904
  subagentSections.push([
1895
1905
  `### ${tid}: ${tTitle}`,
@@ -180,6 +180,14 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
180
180
  }
181
181
  }
182
182
  }
183
+ function isProjectGsdSymlink(basePath) {
184
+ try {
185
+ return lstatSyncFn(join(basePath, ".gsd")).isSymbolicLink();
186
+ }
187
+ catch {
188
+ return false;
189
+ }
190
+ }
183
191
  // ─── Build Artifact Auto-Resolve ─────────────────────────────────────────────
184
192
  /** Patterns for machine-generated build artifacts that can be safely
185
193
  * auto-resolved by accepting --theirs during merge. These files are
@@ -1440,10 +1448,17 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1440
1448
  // CONTEXT files into the stash. If stash pop later fails, those files
1441
1449
  // are permanently trapped in the stash entry and lost on the next
1442
1450
  // stash push or drop.
1451
+ //
1452
+ // When `.gsd` itself is a symlink, Git rejects pathspecs below it
1453
+ // ("beyond a symbolic link"). In that layout, exclude the whole symlink
1454
+ // and keep stashing real project files that could block the merge.
1455
+ const stashPathspecs = isProjectGsdSymlink(originalBasePath_)
1456
+ ? [".", ":(exclude).gsd"]
1457
+ : [":(exclude).gsd/milestones"];
1443
1458
  execFileSync("git", [
1444
1459
  "stash", "push", "--include-untracked",
1445
1460
  "-m", `gsd: pre-merge stash for ${milestoneId}`,
1446
- "--", ":(exclude).gsd/milestones",
1461
+ "--", ...stashPathspecs,
1447
1462
  ], { cwd: originalBasePath_, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
1448
1463
  stashed = true;
1449
1464
  }
@@ -0,0 +1,128 @@
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
+ import { Type } from "@sinclair/typebox";
7
+ import { ensureDbOpen } from "./dynamic-tools.js";
8
+ import { executeGsdGraph, executeMemoryCapture, executeMemoryQuery, } from "../tools/memory-tools.js";
9
+ export function registerMemoryTools(pi) {
10
+ // ─── capture_thought ────────────────────────────────────────────────────
11
+ pi.registerTool({
12
+ name: "capture_thought",
13
+ label: "Capture Thought",
14
+ description: "Record a durable piece of project knowledge (decision, convention, gotcha, pattern, " +
15
+ "preference, or environment detail) into the GSD memory store. Use sparingly — one memory " +
16
+ "per genuinely reusable insight, not per task.",
17
+ promptSnippet: "Capture a durable project insight into the GSD memory store (categories: architecture, convention, gotcha, pattern, preference, environment)",
18
+ promptGuidelines: [
19
+ "Use capture_thought for insights that will remain useful across future sessions.",
20
+ "Do NOT capture one-off bug fixes, temporary state, secrets, or task-specific details.",
21
+ "Keep content to 1–3 sentences.",
22
+ "Set confidence: 0.6 tentative, 0.8 solid, 0.95 well-confirmed (default 0.8).",
23
+ ],
24
+ parameters: Type.Object({
25
+ category: Type.Union([
26
+ Type.Literal("architecture"),
27
+ Type.Literal("convention"),
28
+ Type.Literal("gotcha"),
29
+ Type.Literal("preference"),
30
+ Type.Literal("environment"),
31
+ Type.Literal("pattern"),
32
+ ], { description: "Memory category" }),
33
+ content: Type.String({ description: "The memory text (1–3 sentences, no secrets)" }),
34
+ confidence: Type.Optional(Type.Number({ description: "0.1–0.99, default 0.8", minimum: 0.1, maximum: 0.99 })),
35
+ tags: Type.Optional(Type.Array(Type.String(), { description: "Free-form tags (reserved for future use)" })),
36
+ scope: Type.Optional(Type.String({ description: "Scope name (reserved for future use; defaults to project)" })),
37
+ }),
38
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
39
+ const ok = await ensureDbOpen();
40
+ if (!ok) {
41
+ return {
42
+ content: [{ type: "text", text: "Error: GSD database is not available. Cannot capture memory." }],
43
+ details: { operation: "memory_capture", error: "db_unavailable" },
44
+ isError: true,
45
+ };
46
+ }
47
+ return executeMemoryCapture(params);
48
+ },
49
+ });
50
+ // ─── memory_query ───────────────────────────────────────────────────────
51
+ pi.registerTool({
52
+ name: "memory_query",
53
+ label: "Query Memory",
54
+ description: "Search the GSD memory store for relevant memories. Phase 1 uses keyword matching ranked " +
55
+ "by confidence and reinforcement; future phases add semantic (embedding) retrieval.",
56
+ promptSnippet: "Search the GSD memory store by keyword; returns ranked memories with id, category, and content",
57
+ promptGuidelines: [
58
+ "Use memory_query when you need durable project context that may not be in the current prompt.",
59
+ "Provide a short keyword-style query — not a full question.",
60
+ "Use category to narrow results to gotchas, conventions, architecture notes, etc.",
61
+ ],
62
+ parameters: Type.Object({
63
+ query: Type.String({ description: "Keyword query (2+ char terms)" }),
64
+ k: Type.Optional(Type.Number({ description: "Max results (default 10, max 50)", minimum: 1, maximum: 50 })),
65
+ category: Type.Optional(Type.Union([
66
+ Type.Literal("architecture"),
67
+ Type.Literal("convention"),
68
+ Type.Literal("gotcha"),
69
+ Type.Literal("preference"),
70
+ Type.Literal("environment"),
71
+ Type.Literal("pattern"),
72
+ ], { description: "Restrict results to a single category" })),
73
+ scope: Type.Optional(Type.String({ description: "Only include memories with this scope (e.g. 'project', 'global')" })),
74
+ tag: Type.Optional(Type.String({ description: "Only include memories tagged with this value" })),
75
+ include_superseded: Type.Optional(Type.Boolean({ description: "Include superseded memories (default false)" })),
76
+ reinforce_hits: Type.Optional(Type.Boolean({ description: "Increment hit_count on returned memories (default false)" })),
77
+ }),
78
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
79
+ const ok = await ensureDbOpen();
80
+ if (!ok) {
81
+ return {
82
+ content: [{ type: "text", text: "Error: GSD database is not available. Cannot query memory." }],
83
+ details: { operation: "memory_query", error: "db_unavailable" },
84
+ isError: true,
85
+ };
86
+ }
87
+ return executeMemoryQuery(params);
88
+ },
89
+ });
90
+ // ─── gsd_graph ──────────────────────────────────────────────────────────
91
+ pi.registerTool({
92
+ name: "gsd_graph",
93
+ label: "GSD Knowledge Graph",
94
+ description: "Inspect the relationship graph between memories. mode=query walks supersedes edges from a " +
95
+ "given memoryId; mode=build is a placeholder that future phases will use to rebuild graph " +
96
+ "edges from milestone LEARNINGS artifacts.",
97
+ promptSnippet: "Query the memory relationship graph or trigger a rebuild",
98
+ promptGuidelines: [
99
+ "Use mode=query with a memoryId when you want to see how a memory relates to others.",
100
+ "Phase 1 only exposes supersedes edges; additional relation types arrive in later phases.",
101
+ ],
102
+ parameters: Type.Object({
103
+ mode: Type.Union([Type.Literal("build"), Type.Literal("query")], {
104
+ description: "build = recompute graph (placeholder), query = inspect edges",
105
+ }),
106
+ memoryId: Type.Optional(Type.String({ description: "Memory ID (required when mode=query)" })),
107
+ depth: Type.Optional(Type.Number({ description: "Hops to traverse (0–5, default 1)", minimum: 0, maximum: 5 })),
108
+ rel: Type.Optional(Type.Union([
109
+ Type.Literal("related_to"),
110
+ Type.Literal("depends_on"),
111
+ Type.Literal("contradicts"),
112
+ Type.Literal("elaborates"),
113
+ Type.Literal("supersedes"),
114
+ ], { description: "Only include edges with this relation type" })),
115
+ }),
116
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
117
+ const ok = await ensureDbOpen();
118
+ if (!ok) {
119
+ return {
120
+ content: [{ type: "text", text: "Error: GSD database is not available." }],
121
+ details: { operation: "gsd_graph", error: "db_unavailable" },
122
+ isError: true,
123
+ };
124
+ }
125
+ return executeGsdGraph(params);
126
+ },
127
+ });
128
+ }
@@ -5,6 +5,7 @@ import { loadEcosystemExtensions } from "../ecosystem/loader.js";
5
5
  import { registerDbTools } from "./db-tools.js";
6
6
  import { registerDynamicTools } from "./dynamic-tools.js";
7
7
  import { registerJournalTools } from "./journal-tools.js";
8
+ import { registerMemoryTools } from "./memory-tools.js";
8
9
  import { registerQueryTools } from "./query-tools.js";
9
10
  import { registerHooks } from "./register-hooks.js";
10
11
  import { registerShortcuts } from "./register-shortcuts.js";
@@ -74,6 +75,7 @@ export function registerGsdExtension(pi) {
74
75
  ["db-tools", () => registerDbTools(pi)],
75
76
  ["journal-tools", () => registerJournalTools(pi)],
76
77
  ["query-tools", () => registerQueryTools(pi)],
78
+ ["memory-tools", () => registerMemoryTools(pi)],
77
79
  ["shortcuts", () => registerShortcuts(pi)],
78
80
  ["hooks", () => registerHooks(pi, ecosystemHandlers)],
79
81
  ["ecosystem", () => {
@@ -89,10 +89,23 @@ export async function buildBeforeAgentStartResult(event, ctx) {
89
89
  }
90
90
  let memoryBlock = "";
91
91
  try {
92
- const { formatMemoriesForPrompt, getActiveMemoriesRanked } = await import("../memory-store.js");
93
- const memories = getActiveMemoriesRanked(30);
94
- if (memories.length > 0) {
95
- const formatted = formatMemoriesForPrompt(memories, 2000);
92
+ const { formatMemoriesForPrompt, getActiveMemoriesRanked, queryMemoriesRanked } = await import("../memory-store.js");
93
+ // Always-on "critical" set — small, stable memories that belong in every
94
+ // turn (gotchas, environment, conventions). Ranked by the existing score.
95
+ const CRITICAL_CATEGORIES = new Set(["gotcha", "environment", "convention"]);
96
+ const allRanked = getActiveMemoriesRanked(60);
97
+ const critical = allRanked.filter((m) => CRITICAL_CATEGORIES.has(m.category)).slice(0, 5);
98
+ const criticalIds = new Set(critical.map((m) => m.id));
99
+ // Prompt-relevance set — hybrid FTS5 + (future) semantic retrieval.
100
+ let relevant = [];
101
+ const userPrompt = (event.prompt ?? "").trim();
102
+ if (userPrompt) {
103
+ const hits = queryMemoriesRanked({ query: userPrompt, k: 10 });
104
+ relevant = hits.map((h) => h.memory).filter((m) => !criticalIds.has(m.id));
105
+ }
106
+ const merged = [...critical, ...relevant];
107
+ if (merged.length > 0) {
108
+ const formatted = formatMemoriesForPrompt(merged, 2000);
96
109
  if (formatted) {
97
110
  memoryBlock = `\n\n${formatted}`;
98
111
  }