gsd-pi 2.75.0-dev.2203010a0 → 2.75.0-dev.96d4bb599

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 (109) hide show
  1. package/dist/onboarding.d.ts +5 -1
  2. package/dist/onboarding.js +5 -3
  3. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  4. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +23 -19
  5. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +128 -0
  6. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  7. package/dist/resources/extensions/gsd/bootstrap/system-context.js +17 -4
  8. package/dist/resources/extensions/gsd/commands/handlers/onboarding.js +52 -68
  9. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  10. package/dist/resources/extensions/gsd/commands-memory.js +462 -0
  11. package/dist/resources/extensions/gsd/gsd-db.js +237 -4
  12. package/dist/resources/extensions/gsd/memory-embeddings.js +219 -0
  13. package/dist/resources/extensions/gsd/memory-extractor.js +78 -27
  14. package/dist/resources/extensions/gsd/memory-ingest.js +218 -0
  15. package/dist/resources/extensions/gsd/memory-relations.js +189 -0
  16. package/dist/resources/extensions/gsd/memory-source-store.js +113 -0
  17. package/dist/resources/extensions/gsd/memory-store.js +299 -6
  18. package/dist/resources/extensions/gsd/model-router.js +9 -5
  19. package/dist/resources/extensions/gsd/notification-overlay.js +7 -22
  20. package/dist/resources/extensions/gsd/tools/memory-tools.js +306 -0
  21. package/dist/resources/extensions/gsd/tools/skip-slice.js +78 -0
  22. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  23. package/dist/web/standalone/.next/BUILD_ID +1 -1
  24. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  25. package/dist/web/standalone/.next/build-manifest.json +2 -2
  26. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  27. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.html +1 -1
  44. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  51. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  53. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  54. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  55. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  56. package/package.json +1 -1
  57. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  58. package/packages/mcp-server/dist/server.js +12 -10
  59. package/packages/mcp-server/dist/server.js.map +1 -1
  60. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  61. package/packages/mcp-server/dist/session-manager.js +8 -1
  62. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  63. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
  64. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  65. package/packages/mcp-server/dist/workflow-tools.js +113 -14
  66. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  67. package/packages/mcp-server/src/mcp-server.test.ts +40 -4
  68. package/packages/mcp-server/src/server.ts +12 -10
  69. package/packages/mcp-server/src/session-manager.ts +10 -3
  70. package/packages/mcp-server/src/workflow-tools.test.ts +91 -1
  71. package/packages/mcp-server/src/workflow-tools.ts +128 -18
  72. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  73. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  74. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +24 -20
  75. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +158 -0
  76. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  77. package/src/resources/extensions/gsd/bootstrap/system-context.ts +20 -4
  78. package/src/resources/extensions/gsd/commands/handlers/onboarding.ts +65 -98
  79. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  80. package/src/resources/extensions/gsd/commands-memory.ts +551 -0
  81. package/src/resources/extensions/gsd/gsd-db.ts +273 -4
  82. package/src/resources/extensions/gsd/memory-embeddings.ts +235 -0
  83. package/src/resources/extensions/gsd/memory-extractor.ts +100 -34
  84. package/src/resources/extensions/gsd/memory-ingest.ts +286 -0
  85. package/src/resources/extensions/gsd/memory-relations.ts +240 -0
  86. package/src/resources/extensions/gsd/memory-source-store.ts +138 -0
  87. package/src/resources/extensions/gsd/memory-store.ts +351 -7
  88. package/src/resources/extensions/gsd/model-router.ts +10 -5
  89. package/src/resources/extensions/gsd/notification-overlay.ts +9 -19
  90. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
  91. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  92. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  93. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -2
  94. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  95. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  96. package/src/resources/extensions/gsd/tests/memory-embeddings.test.ts +213 -0
  97. package/src/resources/extensions/gsd/tests/memory-ingest.test.ts +153 -0
  98. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +107 -0
  99. package/src/resources/extensions/gsd/tests/memory-relations.test.ts +175 -0
  100. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  101. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +295 -0
  102. package/src/resources/extensions/gsd/tests/model-router.test.ts +50 -0
  103. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +56 -37
  104. package/src/resources/extensions/gsd/tests/skip-slice-cascades-tasks.test.ts +125 -0
  105. package/src/resources/extensions/gsd/tools/memory-tools.ts +380 -0
  106. package/src/resources/extensions/gsd/tools/skip-slice.ts +133 -0
  107. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  108. /package/dist/web/standalone/.next/static/{8FZqxNe9FxQDmsbRzR8tA → o61X3klsB6C0UE0X1x3PA}/_buildManifest.js +0 -0
  109. /package/dist/web/standalone/.next/static/{8FZqxNe9FxQDmsbRzR8tA → o61X3klsB6C0UE0X1x3PA}/_ssgManifest.js +0 -0
@@ -20,6 +20,10 @@ type PicoModule = {
20
20
  red: (s: string) => string;
21
21
  reset: (s: string) => string;
22
22
  };
23
+ interface RunOnboardingOptions {
24
+ /** Show logo + intro banner. Disable when onboarding is launched inside an active TUI session. */
25
+ showIntro?: boolean;
26
+ }
23
27
  /**
24
28
  * Determine if the onboarding wizard should run.
25
29
  *
@@ -46,7 +50,7 @@ export declare function shouldRunOnboarding(authStorage: AuthStorage, settingsDe
46
50
  * All steps are skippable. All errors are recoverable.
47
51
  * Writes status to stderr during execution.
48
52
  */
49
- export declare function runOnboarding(authStorage: AuthStorage): Promise<void>;
53
+ export declare function runOnboarding(authStorage: AuthStorage, opts?: RunOnboardingOptions): Promise<void>;
50
54
  export declare function runLlmStep(p: ClackModule, pc: PicoModule, authStorage: AuthStorage): Promise<boolean>;
51
55
  export declare function runWebSearchStep(p: ClackModule, pc: PicoModule, authStorage: AuthStorage, isAnthropicAuth: boolean): Promise<string | null>;
52
56
  export declare function runToolKeysStep(p: ClackModule, pc: PicoModule, authStorage: AuthStorage): Promise<number>;
@@ -194,7 +194,7 @@ export function shouldRunOnboarding(authStorage, settingsDefaultProvider) {
194
194
  * All steps are skippable. All errors are recoverable.
195
195
  * Writes status to stderr during execution.
196
196
  */
197
- export async function runOnboarding(authStorage) {
197
+ export async function runOnboarding(authStorage, opts = {}) {
198
198
  let p;
199
199
  let pc;
200
200
  try {
@@ -207,8 +207,10 @@ export async function runOnboarding(authStorage) {
207
207
  return;
208
208
  }
209
209
  // ── Intro ─────────────────────────────────────────────────────────────────
210
- process.stderr.write(renderLogo(pc.cyan));
211
- p.intro(pc.bold('Welcome to GSD — let\'s get you set up'));
210
+ if (opts.showIntro !== false) {
211
+ process.stderr.write(renderLogo(pc.cyan));
212
+ p.intro(pc.bold('Welcome to GSD — let\'s get you set up'));
213
+ }
212
214
  const completedSteps = [];
213
215
  // ── LLM Provider Selection ────────────────────────────────────────────────
214
216
  const llmResult = await runStep(p, 'LLM setup failed', () => runLlmStep(p, pc, authStorage), {
@@ -150,7 +150,7 @@ sessionModelOverride) {
150
150
  const shouldClassify = !isHook || routingConfig.hooks !== false;
151
151
  if (shouldClassify) {
152
152
  let classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct, taskMetadataForPolicy);
153
- const availableModelIds = routingEligibleModels.map(m => m.id);
153
+ const availableModelIds = routingEligibleModels.map(m => `${m.provider}/${m.id}`);
154
154
  // Escalate tier on retry when escalate_on_failure is enabled (default: true)
155
155
  if (retryContext?.isRetry &&
156
156
  retryContext.previousTier &&
@@ -708,28 +708,23 @@ export function registerDbTools(pi) {
708
708
  };
709
709
  }
710
710
  try {
711
- const { getSlice, updateSliceStatus } = await import("../gsd-db.js");
711
+ const { handleSkipSlice } = await import("../tools/skip-slice.js");
712
712
  const { invalidateStateCache } = await import("../state.js");
713
- const slice = getSlice(params.milestoneId, params.sliceId);
714
- if (!slice) {
713
+ const result = handleSkipSlice({
714
+ milestoneId: params.milestoneId,
715
+ sliceId: params.sliceId,
716
+ reason: params.reason,
717
+ });
718
+ if (result.error) {
715
719
  return {
716
- content: [{ type: "text", text: `Error: Slice ${params.sliceId} not found in milestone ${params.milestoneId}` }],
717
- details: { operation: "skip_slice", error: "slice_not_found" },
720
+ content: [{ type: "text", text: `Error: ${result.error}` }],
721
+ details: {
722
+ operation: "skip_slice",
723
+ error: result.error,
724
+ errorCode: result.errorCode ?? "skip_failed",
725
+ },
718
726
  };
719
727
  }
720
- if (slice.status === "complete" || slice.status === "done") {
721
- return {
722
- content: [{ type: "text", text: `Error: Slice ${params.sliceId} is already complete — cannot skip.` }],
723
- details: { operation: "skip_slice", error: "already_complete" },
724
- };
725
- }
726
- if (slice.status === "skipped") {
727
- return {
728
- content: [{ type: "text", text: `Slice ${params.sliceId} is already skipped.` }],
729
- details: { operation: "skip_slice", sliceId: params.sliceId, milestoneId: params.milestoneId },
730
- };
731
- }
732
- updateSliceStatus(params.milestoneId, params.sliceId, "skipped");
733
728
  invalidateStateCache();
734
729
  // Rebuild STATE.md so it reflects the skip immediately (#3477).
735
730
  // Without this, /gsd auto reads stale STATE.md and resumes the skipped slice.
@@ -741,13 +736,20 @@ export function registerDbTools(pi) {
741
736
  catch (err) {
742
737
  logError("tool", `skip_slice rebuildState failed: ${err.message}`, { tool: "gsd_skip_slice" });
743
738
  }
739
+ const suffix = result.wasAlreadySkipped
740
+ ? result.tasksSkipped > 0
741
+ ? ` (already skipped; cascaded ${result.tasksSkipped} leftover task(s) to skipped).`
742
+ : " (already skipped; no pending tasks to cascade)."
743
+ : ` Cascaded ${result.tasksSkipped} task(s) to skipped. Auto-mode will advance past this slice.`;
744
744
  return {
745
- content: [{ type: "text", text: `Skipped slice ${params.sliceId} (${params.milestoneId}). Reason: ${params.reason ?? "User-directed skip"}. Auto-mode will advance past this slice.` }],
745
+ content: [{ type: "text", text: `Skipped slice ${params.sliceId} (${params.milestoneId}). Reason: ${params.reason ?? "User-directed skip"}.${suffix}` }],
746
746
  details: {
747
747
  operation: "skip_slice",
748
748
  sliceId: params.sliceId,
749
749
  milestoneId: params.milestoneId,
750
750
  reason: params.reason,
751
+ tasksSkipped: result.tasksSkipped,
752
+ wasAlreadySkipped: result.wasAlreadySkipped,
751
753
  },
752
754
  };
753
755
  }
@@ -764,12 +766,14 @@ export function registerDbTools(pi) {
764
766
  name: "gsd_skip_slice",
765
767
  label: "Skip Slice",
766
768
  description: "Mark a slice as skipped so auto-mode advances past it without executing. " +
769
+ "Non-closed tasks within the slice are cascaded to skipped so milestone completion is not blocked by leftover pending tasks (#4375). " +
767
770
  "The slice data is preserved for reference. The state machine treats skipped slices like completed ones for dependency satisfaction.",
768
771
  promptSnippet: "Skip a GSD slice (mark as skipped, auto-mode will advance past it)",
769
772
  promptGuidelines: [
770
773
  "Use gsd_skip_slice when a slice should be bypassed — descoped, superseded, or no longer relevant.",
771
774
  "Cannot skip a slice that is already complete.",
772
775
  "Skipped slices satisfy downstream dependencies just like completed slices.",
776
+ "All pending/active tasks in the slice are cascaded to skipped; completed tasks are never downgraded.",
773
777
  ],
774
778
  parameters: Type.Object({
775
779
  sliceId: Type.String({ description: "Slice ID (e.g. S02)" }),
@@ -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
  }
@@ -1,31 +1,26 @@
1
- // GSD — /gsd onboarding command handler (re-entry, --resume, --reset, --step)
1
+ // GSD — /gsd onboarding command handler (re-entry hub)
2
2
  //
3
- // Provides the discoverable re-entry point for the onboarding wizard. The
4
- // first-run wizard in src/onboarding.ts is hidden behind shouldRunOnboarding;
5
- // this handler lets users re-launch it on demand.
6
- import { AuthStorage } from "@gsd/pi-coding-agent";
7
- import { homedir } from "node:os";
8
- import { join } from "node:path";
9
- import { ONBOARDING_STEPS, isValidStepId, nearestResumeStep, } from "../../setup-catalog.js";
3
+ // The first-run wizard in src/onboarding.ts uses @clack/prompts and takes over
4
+ // raw stdin. Running it from inside the pi-coding-agent TUI wedges the TUI
5
+ // (clack leaves stdin paused + cooked, pi-tui's data handler then receives no
6
+ // keypresses). So re-entry cannot replay the clack wizard — instead it routes
7
+ // to a setup hub built from ctx.ui.select, which the TUI owns.
8
+ //
9
+ // Clack-only steps (llm/search/remote/tool-keys via the first-run wizard) are
10
+ // surfaced as notifications pointing the user at the canonical per-step
11
+ // commands (/login, /gsd keys, /gsd remote) that are already ctx.ui-safe.
12
+ import { ONBOARDING_STEPS, isValidStepId, } from "../../setup-catalog.js";
10
13
  import { isOnboardingComplete, readOnboardingRecord, resetOnboarding, } from "../../onboarding-state.js";
11
- // Inline auth path (mirrors src/app-paths.ts) — keep this module rootDir-clean
12
- // for the resources tsconfig. Importing from src/ pulls files outside
13
- // src/resources and breaks the build.
14
- const AUTH_FILE_PATH = join(process.env.GSD_CODING_AGENT_DIR ||
15
- join(process.env.GSD_HOME || join(homedir(), ".gsd"), "agent"), "auth.json");
16
- async function loadFirstRunWizard() {
17
- const specifier = "../../../../../onboarding.js";
18
- return (await import(/* @vite-ignore */ specifier));
19
- }
20
14
  function parseArgs(raw) {
21
15
  const tokens = raw.split(/\s+/).filter(Boolean);
22
- const out = { resume: false, reset: false, step: null, stepValid: null };
16
+ const out = { reset: false, step: null, stepValid: null };
23
17
  for (let i = 0; i < tokens.length; i++) {
24
18
  const t = tokens[i];
25
- if (t === "--resume" || t === "resume")
26
- out.resume = true;
27
- else if (t === "--reset" || t === "reset")
19
+ if (t === "--reset" || t === "reset")
28
20
  out.reset = true;
21
+ else if (t === "--resume" || t === "resume") {
22
+ // Re-entry no longer replays the wizard; --resume collapses into the hub.
23
+ }
29
24
  else if (t === "--step" || t === "step") {
30
25
  const next = tokens[i + 1];
31
26
  if (next) {
@@ -42,46 +37,26 @@ function parseArgs(raw) {
42
37
  }
43
38
  return out;
44
39
  }
45
- async function getAuthStorage() {
46
- return AuthStorage.create(AUTH_FILE_PATH);
47
- }
48
- async function runWholeWizard(ctx, fromStep) {
49
- const authStorage = await getAuthStorage();
50
- // The first-run wizard ignores the resume hint today — it always walks the
51
- // full sequence with skip prompts. We still mark completion at the end and
52
- // record the resume hint for next time. This keeps the wizard linear and
53
- // simple; per-step jump support comes via --step.
54
- if (fromStep) {
55
- ctx.ui.notify(`Resuming from step: ${fromStep}. The wizard runs all remaining steps; press skip on any you've already configured.`, "info");
56
- }
57
- const { runOnboarding } = await loadFirstRunWizard();
58
- await runOnboarding(authStorage);
59
- }
60
- async function runSingleStep(ctx, stepId) {
61
- const authStorage = await getAuthStorage();
62
- const ob = await loadFirstRunWizard();
63
- // Lazy-load clack + chalk via the same path the wizard uses
64
- const p = await import("@clack/prompts");
65
- const { default: chalk } = await import("chalk");
66
- const pc = {
67
- cyan: chalk.cyan, green: chalk.green, yellow: chalk.yellow,
68
- dim: chalk.dim, bold: chalk.bold, red: chalk.red, reset: chalk.reset,
69
- };
40
+ // ─── Per-step routing ────────────────────────────────────────────────────────
41
+ //
42
+ // Clack-based steps are surfaced as notifications — running them inline from
43
+ // the TUI would wedge stdin (see header comment). Everything else routes to an
44
+ // existing ctx.ui-safe handler.
45
+ async function runStep(ctx, stepId) {
70
46
  switch (stepId) {
71
47
  case "llm":
72
- await ob.runLlmStep(p, pc, authStorage);
48
+ ctx.ui.notify("LLM provider setup: run /login to sign in via OAuth, or /gsd keys add to paste an API key.", "info");
73
49
  return;
74
50
  case "search":
75
- await ob.runWebSearchStep(p, pc, authStorage, false);
51
+ ctx.ui.notify("Web search setup: run /gsd keys add and pick a search provider (brave, tavily, etc.).", "info");
76
52
  return;
77
53
  case "remote":
78
- await ob.runRemoteQuestionsStep(p, pc, authStorage);
54
+ ctx.ui.notify("Remote questions setup: run /gsd remote to configure Discord / Slack / Telegram notifications.", "info");
79
55
  return;
80
56
  case "tool-keys":
81
- await ob.runToolKeysStep(p, pc, authStorage);
57
+ ctx.ui.notify("Tool keys setup: run /gsd keys add to save API keys for Context7, Jina, Groq voice, etc.", "info");
82
58
  return;
83
59
  case "model": {
84
- // Delegate to /gsd model picker
85
60
  const { handleCoreCommand } = await import("./core.js");
86
61
  await handleCoreCommand("model", ctx);
87
62
  return;
@@ -94,7 +69,6 @@ async function runSingleStep(ctx, stepId) {
94
69
  return;
95
70
  }
96
71
  case "doctor": {
97
- // Best-effort: surface provider doctor results inline
98
72
  try {
99
73
  const { runProviderDoctor } = await import("../../doctor-providers.js");
100
74
  if (typeof runProviderDoctor === "function") {
@@ -106,10 +80,9 @@ async function runSingleStep(ctx, stepId) {
106
80
  ctx.ui.notify("Run /gsd doctor to validate your setup.", "info");
107
81
  return;
108
82
  }
109
- case "skills": {
110
- ctx.ui.notify("Skill install runs automatically during /gsd init. Use /gsd init or /skill manage.", "info");
83
+ case "skills":
84
+ ctx.ui.notify("Skill install runs during /gsd init. Use /gsd init or /skill manage.", "info");
111
85
  return;
112
- }
113
86
  case "project": {
114
87
  const { handleCoreCommand } = await import("./core.js");
115
88
  await handleCoreCommand("init", ctx);
@@ -117,6 +90,25 @@ async function runSingleStep(ctx, stepId) {
117
90
  }
118
91
  }
119
92
  }
93
+ // ─── Setup hub ───────────────────────────────────────────────────────────────
94
+ async function renderSetupHub(ctx) {
95
+ const record = readOnboardingRecord();
96
+ const completed = new Set(record.completedSteps);
97
+ const skipped = new Set(record.skippedSteps);
98
+ const labels = ONBOARDING_STEPS.map(step => {
99
+ const mark = completed.has(step.id) ? "✓" : skipped.has(step.id) ? "↷" : "○";
100
+ const req = step.required ? " (required)" : "";
101
+ return `${mark} ${step.label}${req} — ${step.hint}`;
102
+ });
103
+ const labelToStep = new Map(labels.map((label, i) => [label, ONBOARDING_STEPS[i].id]));
104
+ const choice = await ctx.ui.select("GSD Setup — pick a step to configure", labels);
105
+ if (typeof choice !== "string")
106
+ return;
107
+ const stepId = labelToStep.get(choice);
108
+ if (!stepId)
109
+ return;
110
+ await runStep(ctx, stepId);
111
+ }
120
112
  function renderStatus() {
121
113
  const r = readOnboardingRecord();
122
114
  const lines = ["GSD Onboarding\n"];
@@ -149,27 +141,19 @@ export async function handleOnboarding(rawArgs, ctx) {
149
141
  ctx.ui.notify(`Unknown step "${args.step}". Valid: ${validIds}`, "warning");
150
142
  return;
151
143
  }
152
- await runSingleStep(ctx, args.step);
144
+ await runStep(ctx, args.step);
153
145
  return;
154
146
  }
155
147
  if (args.reset) {
156
148
  resetOnboarding();
157
- ctx.ui.notify("Onboarding reset. Existing API keys/credentials are unchanged — manage them with /gsd keys.", "info");
158
- await runWholeWizard(ctx);
149
+ ctx.ui.notify("Onboarding state cleared. API keys/credentials are unchanged — manage them with /gsd keys. Restart GSD to re-run the first-run wizard, or pick a step below.", "info");
150
+ await renderSetupHub(ctx);
159
151
  return;
160
152
  }
161
- if (args.resume) {
162
- const r = readOnboardingRecord();
163
- const next = nearestResumeStep(r.lastResumePoint, r.completedSteps);
164
- await runWholeWizard(ctx, next);
165
- return;
166
- }
167
- // No flags. If already complete, show status + offer choice.
153
+ // No flags (or --resume). Show status if complete, then open the hub.
168
154
  if (isOnboardingComplete()) {
169
155
  ctx.ui.notify(renderStatus(), "info");
170
- ctx.ui.notify("Onboarding already complete. Use /gsd onboarding --reset to start over, or --step <name> to redo one section.", "info");
171
- return;
172
156
  }
173
- await runWholeWizard(ctx);
157
+ await renderSetupHub(ctx);
174
158
  }
175
159
  export { renderStatus as renderOnboardingStatus };
@@ -248,6 +248,11 @@ Examples:
248
248
  await handleExtractLearnings(trimmed.replace(/^extract-learnings\s*/, "").trim(), ctx, pi);
249
249
  return true;
250
250
  }
251
+ if (trimmed === "memory" || trimmed.startsWith("memory ") || trimmed === "memory help") {
252
+ const { handleMemory } = await import("../../commands-memory.js");
253
+ await handleMemory(trimmed.replace(/^memory\s*/, "").trim(), ctx, pi);
254
+ return true;
255
+ }
251
256
  if (trimmed === "scan" || trimmed.startsWith("scan ")) {
252
257
  const { handleScan } = await import("../../commands-scan.js");
253
258
  // \s* (not \s+) is intentional: handles both /gsd scan (no args) and /gsd scan --focus X